Stéphane de Luca Stéphane de Luca - 1 month ago 17
AngularJS Question

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

Answer

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!

Comments