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
.
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.
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.
WKWebView
To easily use this, I've extended the WKWebView
class.
// 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)
})
}
}
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
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With