Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WKWebView in iOS: How can I intercept a click and retrieve the linked content instead?

In a WKWebView, when a user clicks a link that refers to certain file types (e.g. a VCF file for contacts, or an ICS file for calendar events), I'd like to intercept the link, i.e. cancel the navigation, and instead display the content using a specialized view controller.

For example, the CNContactViewController can be used to display contacts, the EKEventViewController can be used to display calendar events.

I can intercept the click by assigning a WKNavigationDelegate and using decidePolicyForNavigationAction:

// Swift 2
extension MyController: WKNavigationDelegate {
  func webView(webView: WKWebView, decidePolicyForNavigationAction 
      navigationAction: WKNavigationAction, 
      decisionHandler: (WKNavigationActionPolicy) -> ()) {

    let url = navigationAction.request.URL!

    if url.pathExtension == "ics" {
      decisionHandler(WKNavigationActionPolicy.Cancel)
      // TODO: download data
      // TODO: display with EKEventViewController
    } else if url.pathExtension == "vcf" {
      decisionHandler(WKNavigationActionPolicy.Cancel)
      // TODO: download data
      // TODO: display with CNContactViewController
    } else {
      decisionHandler(WKNavigationActionPolicy.Allow)
    }

  }
}

But in order to display the files the specialized controllers, I need to download the data from the given url first.

How can I do that?

Since the download requires authentication, the download needs to share the cookies with the WKWebView, or use another technique to share the already authenticated session.

If it helps: I've already got access to the web view's WKProcessPool and WKWebViewConfiguration. To my understanding, the cookies are somehow tied to the WKProcessPool. But I don't know how to use this to download the content, for example with a NSURLSession.

like image 953
fiedl Avatar asked Nov 09 '22 10:11

fiedl


1 Answers

It feels hacky, but I solved this by having the WKWebView execute some javascript that retrieves the content via ajax and returns it to a completionHandler in swift.

Background

The WKWebView supports calling evaluateJavaScript, which passes the javascript's result to a completionHandler:

func evaluateJavaScript(_ javaScriptString: String,
    completionHandler completionHandler: ((AnyObject?, NSError?) -> Void)?)

Since there's jQuery on the server side, I used this to send an ajax request like follows. But, of course, this can be done with vanilla javascript as well.

(function(url) {
  var result = '';
  $.ajax({
    type: 'GET',
    url: url, 
    success: function(r) {result = r}, 
    failure: function() {result = null}, 
    async: false
  }); 
  return result 
})(url)

The url can be passed to javascript with swift's string interpolation.

Extend WKWebView

To easily use this, I've extended the WKWebViewclass.

// Views/WKWebView.swift
import WebKit

extension WKWebView {
  func readUrlContent(url: NSURL, completionHandler: (result: String) -> Void) {

    self.evaluateJavaScript("(function() { var result = ''; $.ajax({type: 'GET', url: '\(url)', success: function(r) {result = r}, failure: function() {result = null}, async: false }); return result })()", completionHandler: { (response, error) -> Void in

      let result = response as! String
      completionHandler(result: result)

    })

  }
}

Usage

From the question's example, this can be called like this:

let url = navigationAction.request.URL!
if url.pathExtension == "ics" {
  decisionHandler(WKNavigationActionPolicy.Cancel)
  webView.readUrlContent(url) { (result: String) in
    print(result)
    // TODO: display with EKEventViewController
  }
}
like image 53
fiedl Avatar answered Nov 14 '22 22:11

fiedl