Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AngularJS on WKWebView: any solution for handling file:// on iOS 9?

I am porting a huge angularJS app to iOS 9 and wanted to benefit from WKWebView (migrating from UIWebView). The app is locally self contained, hence all files are served for the app main bundle using file:// protocol.

Unfortunately, it sounds WKWebView originally breaks file:// protocol on iOS 8.x, but some light were casted when I saw the brand new iOS 9 loadFileURL(basePath:, allowingReadAccessToURL:) API.

let readAccessPath = NSURL(string:"app", relativeToURL:bundleURL)?.absoluteURL
webView.loadFileURL(basePath!, allowingReadAccessToURL:readAccessPath!)

Alas, while I set allowingReadAccessToURL to the root folder within my bundle (app/), I only got the "index file", no asynchronous file are loaded.

Anyone having some experience with that issue?

[UPDATE] I see my initial issue description wasn't accurate enough. I do have my HTML running. But my asynchronous angularJS calls are actually blocked by security watchdog in the WebKit framework.

enter image description here enter image description here

like image 945
Stéphane de Luca Avatar asked Oct 13 '15 07:10

Stéphane de Luca


People also ask

What is iOS WKWebView?

A WKWebView object is a platform-native view that you use to incorporate web content seamlessly into your app's UI. A web view supports a full web-browsing experience, and presents HTML, CSS, and JavaScript content alongside your app's native views.

Is WKWebView the same as Safari?

WKWebView. WKWebView was introduced in iOS 8 allowing app developers to implement a web browsing interface similar to that of mobile Safari. This is due, in part, to the fact that WKWebView uses the Nitro Javascript engine, the same engine used by mobile Safari.

What is use of UIWebview or WKWebView?

Difference Between UIWebview and WKWebViewUIWebview is a part of UIKit, so it is available to your apps as standard. You don't need to import anything, it will we there by default. But WKWebView is run in a separate process to your app,. You need to import Webkit to use WKWebView in your app.


3 Answers

Using GCDWebServer is what I would recommend, you can run a local web server as http://localhost.

Swift 3.0

  1. Add GCDWebServer pod pod "GCDWebServer", "~> 3.0"

  2. Click and drag your AngularJS web folder into Xcode and when prompted select 'Copy items if needed' and 'Create folder references'

  3. Use this code in your controller to run a localhost web server

    class MainViewController: UIViewController, WKNavigationDelegate, WKScriptMessageHandler, WKUIDelegate, GIDSignInUIDelegate {
    
    var webView : WKWebView!
    
    var webServer:GCDWebServer?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.webView.load(URLRequest(url: loadDefaultIndexFile()!))
    }
    
    private func loadDefaultIndexFile() -> URL? {
        self.webServer = GCDWebServer()
        let mainBundle = Bundle.main
        // The path to my index.html is www/index.html.  If using a default public folder then it could be public/index.html
        let folderPath = mainBundle.path(forResource: "www", ofType: nil)
        self.webServer?.addGETHandler(forBasePath: "/", directoryPath: folderPath!, indexFilename: "index.html", cacheAge: 0, allowRangeRequests: true)
    
        do {
            try self.webServer?.start(options: [
                "Port": 3000,
                "BindToLocalhost": true
                ]);
        }catch{
            print(error)
        }
    
    // Path should be http://localhost:3000/index.html
    return self.webServer?.serverURL
    }
    
    }
    
like image 81
Brad Avatar answered Oct 12 '22 17:10

Brad


Although I don't have a quick answer (I mean a quick fix) I do have a solution.

This involves giving up on file:// protocol and switch to http:// over localhost.

SHORT ANSWER

Here are the steps:

1) — Install a local Web server in your own app;

2) — Setup the local Web server to serve from localhost at a given port of your choosing;

3) — Set up the delegate that actually serve the file from your app ressources given the right mime type;

4) — Authorize to bypass iOS9 ATS to handle http (and not https only).

And voila!

DETAILED ANSWER

1) Install a local Web server in your own app;

Install the GCDWebServer fro its Github repo: https://github.com/swisspol/GCDWebServer

2) Setup the local Web server to serve from localhost at a given port of your

Given the fact your angularjs or HTML app files are located to the folder "app" in your resources folder.

In your vc ViewDidLoad:

@implementation ViewController

GCDWebServer* _webServer;

- (void)viewDidLoad
{
    [super viewDidLoad];
    self.webView = [[WKWebView alloc] initWithFrame:self.view.frame];
    [self.view addSubview:self.webView];

    self.webView.navigationDelegate = self;

    NSURL *bundleURL = [NSBundle mainBundle].bundleURL;
    NSURL *basePath = nil;

    // Init WebServer

    [self initWebServer:[[NSURL URLWithString:@"app" relativeToURL:bundleURL] absoluteURL]];

    basePath = [NSURL URLWithString:@"http://localhost:8080/page.html#/home" relativeToURL:nil];
    NSURLRequest *request = [[NSURLRequest alloc] initWithURL:basePath];
    [self.webView loadRequest:request];
}

3) Set up the delegate that actually serve the file from your app ressources given the right mime type;

-(void)initWebServer:(NSURL *)basePath {
    // Create server
    _webServer = [[GCDWebServer alloc] init];

    #define GCDWebServer_DEBUG 0
    #define GCDWebServer_VERBOSE 1
    #define GCDWebServer_INFO 2
    #define GCDWebServer_WARNING 3
    #define GCDWebServer_ERROR 4
    #define GCDWebServer_EXCEPTION 5

    [GCDWebServer setLogLevel:GCDWebServer_ERROR];
    // Add a handler to respond to GET requests on any URL
    [_webServer addDefaultHandlerForMethod:@"GET"
                              requestClass:[GCDWebServerRequest class]
                              processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {



                                  //NSLog([NSString stringWithFormat:@"WS: loading %@", request]);
                                  NSString * page = request.URL.lastPathComponent;
                                  NSString * path = request.URL.path;
                                  NSString * file = path;

                                  //NSLog(@"WS: loading %@", file);

                                  NSString * fullPath = [NSString stringWithFormat:@"%@%@", basePath, path];
                                  NSString * sFullPath = [fullPath substringFromIndex:7];

                                  BOOL isText = NO;

                                  if([page.lastPathComponent hasSuffix:@"html"]) {
                                      isText = YES;
                                  }



                                  if (isText) {
                                      NSError * error = nil;
                                      NSString * html = [NSString stringWithContentsOfFile:sFullPath encoding:NSUTF8StringEncoding error: &error];
                                      return [GCDWebServerDataResponse responseWithHTML:html];
                                  }
                                  else {
                                      NSData * data = [NSData dataWithContentsOfFile:sFullPath];
                                      if (data !=nil) {

                                          NSString * type = @"image/jpeg";

                                          if      ([page.lastPathComponent hasSuffix:@"jpg"]) type = @"image/jpeg";
                                          else if ([page.lastPathComponent hasSuffix:@"png"]) type = @"image/png";
                                          else if ([page.lastPathComponent hasSuffix:@"css"]) type = @"text/css";
                                          else if ([page.lastPathComponent hasSuffix:@"js" ]) type = @"text/javascript";


                                          return [GCDWebServerDataResponse responseWithData:data contentType:type];
                                      }
                                      else {

                                          return [GCDWebServerDataResponse responseWithHTML:[NSString stringWithFormat:@"<html><body><p>404 : unknown file %@ World</p></body></html>", sFullPath]];
                                      //return [GCDWebServerDataResponse responseWithHTML:@"<html><body><p>Hello World</p></body></html>"];
                                      }
                                  }
                              }];

    // Start server on port 8080
    [_webServer startWithPort:8080 bonjourName:nil];
    NSLog(@"Visiting %@", _webServer.serverURL);
}

4) Authorize to bypass iOS9 ATS to handle http (and not https only)

In your info.plist file in Xcode, you must add a dictionary named "App Transport Security Settings" with inside a key-value as follows:

NSAllowsArbitraryLoads = true

Hope it helps. Anyone who stumble upon something simpler is welcome to answer!

like image 40
Stéphane de Luca Avatar answered Oct 12 '22 18:10

Stéphane de Luca


As I can say, you are making mobile application and compiling it after... I had quite similar issue. But for me final build was made by adobe build.phonegap service. And there the only thing I had to do is to add cordova-whitelist-plugin in config.xml like this

< gap:plugin name="cordova-plugin-whitelist" source="npm" version="1.0.0" />

And than add permission for accessing such links

<allow-intent href="file:///*/*" />

also into config.xml

Sorry, if I understood you wrong.

like image 26
Anastasiya Avatar answered Oct 12 '22 17:10

Anastasiya