WKWebView en iOS: ¿Cómo puedo interceptar un clic y recuperar el contenido vinculado en su lugar?

En un WKWebView , cuando un usuario hace clic en un enlace que hace reference a ciertos types de files (por ejemplo, un file VCF para contactos o un file ICS para events del calendar), me gustaría interceptar el enlace, es decir, cancelar la navigation y mostrar el contenido que usa un controller de vista especializado.

Por ejemplo, el CNContactViewController se puede usar para mostrar contactos, el EKEventViewController se puede usar para mostrar los events del calendar.

Puedo interceptar el clic asignando un WKNavigationDelegate y usando 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) } } } 

Pero para mostrar los files de los controlleres especializados, primero necesito download los datos de la url dada.

¿Cómo puedo hacer eso?

Dado que la descarga requiere authentication , la descarga debe compartir las cookies con WKWebView , o usar otra técnica para compartir la session ya autenticada.

Si ayuda: ya tengo acceso a WKProcessPool y WKWebViewConfiguration la vista web. A mi entender, las cookies están relacionadas de alguna manera con el WKProcessPool . Pero no sé cómo usar esto para download el contenido, por ejemplo, con un NSURLSession .

Se siente hacky, pero solucioné esto haciendo que el WKWebView ejecutara un poco de javascript que recupera el contenido a través de ajax y lo devuelve a completionHandler en swift.

Fondo

WKWebView admite llamar a evaluateJavaScript , que pasa el resultado del javascript a completionHandler :

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

Como hay jQuery en el lado del server, utilicé esto para enviar una request de ajax como sigue. Pero, por supuesto, esto también se puede hacer con vainilla javascript .

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

La url se puede pasar a JavaScript con la interpolación de cadena de Swift .

Extienda WKWebView

Para usar esto fácilmente, he ampliado la class WKWebView .

 // 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) }) } } 

Uso

Desde el ejemplo de la pregunta, esto se puede llamar así:

 let url = navigationAction.request.URL! if url.pathExtension == "ics" { decisionHandler(WKNavigationActionPolicy.Cancel) webView.readUrlContent(url) { (result: String) in print(result) // TODO: display with EKEventViewController } }