6

I have a HTML file which contains local resource files such as css, js and png files inside its content. These local resource files are in zip format. My app use WKWebView to display this html file. I want to find a solution to intercept the web view request to detect which local resource files are load together with this html file -> then unzip them if they are still zip format.

My HTML data content contains thousands of these local resource file so I can't unzip all of them before display content. With UIWebView, we are using NSURLProtocol subclass to intercept the request, detect local resource files and un-zip it on demand based on the html page which user is viewing.

I am having this issue when convert UIWebView to WKWebView. Similar problem was post here: https://forums.developer.apple.com/thread/87474

======= Update =======>

I figured it out by using WKURLSchemeHandler.

Note: You need to change the file scheme to a custom scheme in order to use WKURLSchemeHandler because it will not work with standard schemes like file, http, https.

1. Register custom scheme with WKWebView

    let configuration = WKWebViewConfiguration()
    configuration.setURLSchemeHandler(self, forURLScheme: "x-file")
    webView = WKWebView(frame: view.bounds, configuration: configuration)

2. Convert file scheme to the custom scheme (x-file) then load it with WKWebView

    let htmlPath = Bundle.main.path(forResource: "index", ofType: "html")
    var htmlURL = URL(fileURLWithPath: htmlPath!, isDirectory: false)                    
    htmlURL = self.changeURLScheme(newScheme: "x-file", forURL: htmlURL)
    self.webView.load(URLRequest(url: htmlURL))


3. Implement 2 methods of WKURLSchemeHandler protocol and handle 3 delegate methods of WKURLSchemeTask.

    func webView(_ webView: WKWebView, start urlSchemeTask: WKURLSchemeTask) {
        print("Function: \(#function), line: \(#line)")
        print("==> \(urlSchemeTask.request.url?.absoluteString ?? "")\n")

        // Your local resource files will be catch here. You can determine it by checking the urlSchemeTask.request.url.
        // From here I will unzip local resource files (js, css, png,...) if they are still in zip format
        ....

        // Handle WKURLSchemeTask delegate methods
        let url = changeURLScheme(newScheme: "file", forURL: urlSchemeTask.request.url!)

        do {
            let data = try Data(contentsOf: url)

            urlSchemeTask.didReceive(URLResponse(url: urlSchemeTask.request.url!, mimeType: "text/html", expectedContentLength: data.count, textEncodingName: nil))
            urlSchemeTask.didReceive(data)
            urlSchemeTask.didFinish()
        } catch {
            print("Unexpected error when get data from URL: \(url)")
        }
    }

    func webView(_ webView: WKWebView, stop urlSchemeTask: WKURLSchemeTask) {
        print("Function: \(#function), line: \(#line)")
        print("==> \(urlSchemeTask.request.url?.absoluteString ?? "")\n")
    }
Tony Pham
  • 303
  • 1
  • 3
  • 8
  • This comment was excellent `urlSchemeTask.request.url`. I couldn't work out why `webView.url` never showed the CSS, JS requests. Thanks again. – rustyMagnet Dec 17 '19 at 14:09
  • 1
    where do you get `changeURLScheme(newScheme: forURL:)` method from? – Piotr Z Feb 28 '20 at 15:15
  • changeURLScheme is our custom method. We just simple change the custom scheme back to file:// scheme - (NSURL *)changeURLSchemeTo:(NSString *)newScheme { NSURLComponents *components = [NSURLComponents componentsWithURL:self resolvingAgainstBaseURL:YES]; if (components) { components.scheme = newScheme; return components.URL ? components.URL : self; } return self; } – Tony Pham Aug 13 '20 at 20:26
  • @TonyPham Isolate your solution to an answer and pick it as the correct answer. – user31208 Feb 17 '21 at 15:19

1 Answers1

4

If WKWebView loads a local html file you could just give the WKWebView access to local app resources like so:

NSURL *documentDirectoryURL = [NSURL fileURLWithPath:DOCUMENTS_DIRECTORY];

// This code gives acces to a file that is outside of our webview html file root directory
[self.webView loadFileURL:documentDirectoryURL allowingReadAccessToURL:documentDirectoryURL];

// If you use one of these loads after, they will too have access if they are in app html files
[self.webView loadRequest:<inAppHTMLFile>];
[self.webView loadHTMLString: baseURL:];

This here gives access to all files in the document directory.

I found your solution helpful when I wanted to link local resources to online pages.

WKUserContentController *contentController = [[WKUserContentController alloc]init];
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc]init];
CustomFileShemeHandler *schemeHandler = [[CustomFileShemeHandler alloc] init];
[config setURLSchemeHandler:schemeHandler forURLScheme:@"myApp-images"];
config.userContentController = contentController;
self.webView = [[WKWebView alloc] initWithFrame:CGRectZero configuration:config];



//CustomFileShemeHandler.m

@implementation CustomFileShemeHandler

- (void)webView:(nonnull WKWebView *)webView startURLSchemeTask:(nonnull id<WKURLSchemeTask>)urlSchemeTask {
    NSURL *customFileURL = urlSchemeTask.request.URL;
    NSString *securityFilter = [NSString stringWithFormat:@"Documents/LocalAppImages"];

    if (![customFileURL.absoluteString containsString:securityFilter]) {
        return;
    }

    NSURL *fileURL = [self changeURLScheme:customFileURL toScheme:@"file"];
    NSURLRequest* fileUrlRequest = [[NSURLRequest alloc] initWithURL:fileURL cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:.1];

    NSURLSession *session = [NSURLSession sharedSession];
    NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:fileUrlRequest completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        NSURLResponse *response2 = [[NSURLResponse alloc] initWithURL:urlSchemeTask.request.URL MIMEType:response.MIMEType expectedContentLength:data.length textEncodingName:nil];
        if(error){
            [urlSchemeTask didFailWithError:error];
        }
        [urlSchemeTask didReceiveResponse:response2];
        [urlSchemeTask didReceiveData:data];
        [urlSchemeTask didFinish];
    }];

    [dataTask resume];
}

- (void)webView:(nonnull WKWebView *)webView stopURLSchemeTask:(nonnull id<WKURLSchemeTask>)urlSchemeTask {
    NSLog(@"DUNNO WHAT TO DO HERE");
}

- (NSURL *)changeURLScheme:(NSURL *)url toScheme:(NSString *)newScheme {
    NSURLComponents *components = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:YES];
    components.scheme = newScheme;
    return components.URL;
}

With this any page open in the webView can link to a local resource using its full path an the custom scheme:

<img src="myApp-images:///<YourDocumentsAppPahtHere>/LocalAppImages/funnyDog.jpg">
h3dkandi
  • 772
  • 6
  • 25
  • i have downloaded the images in a local documents directory folder now i have a string which contains the last path component of img , which are ideally saved in docDirectory , how can i load all the images in that string to wkwebview , can you guide me with steps please. – iMinion Sep 09 '20 at 21:38
  • The delegate doesn't get called when I add the source using evaluate javascript like this - let imageURL : String? = Bundle.main.path(forResource: "diamond", ofType: "png") let js = "document.getElementById('signature').src='custom-scheme://\(String(describing: imageURL))?type=photos';" self.webView.evaluateJavaScript(js) Is there a way to trigger the delegate when evaluate javascript is executed. – nOOb iOS Feb 09 '21 at 18:49