Search code examples
iosangularjsuiwebviewwkwebview

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


Solution

  • 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!