steps - Llamar a la función de JavaScript desde el código nativo en WKWebView
javascript primeros pasos (6)
Usando un WKWebView en iOS 8, ¿cómo puedo ejecutar una función de JavaScript desde el lado nativo o comunicarme desde el lado nativo a JavaScript? No parece haber un método similar al stringByEvaluatingJavaScriptFromString de stringByEvaluatingJavaScriptFromString:
(Puedo usar - addScriptMessageHandler:name:
en el objeto configuration.userContentController
para permitir que la comunicación de JS sea nativa, pero estoy buscando la dirección opuesta).
Detalles
Xcode 9.1, Swift 4
Descripción
La secuencia de comandos se inserta en la página que se mostrará en WKWebView. Este script devolverá la URL de la página (pero puede escribir otro código JavaScript). Esto significa que el evento del script se genera en la página web, pero se manejará en nuestra función:
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {...}
Solución
extension WKUserScript {
class func getUrlScript(scriptName: String) -> String {
return "webkit.messageHandlers./(scriptName).postMessage(document.URL)"
}
}
extension WKWebViewConfiguration {
func addScript(script: String, scriptHandlerName:String, scriptMessageHandler: WKScriptMessageHandler, injectionTime:WKUserScriptInjectionTime) {
let userScript = WKUserScript(source: script, injectionTime: injectionTime, forMainFrameOnly: false)
userContentController.addUserScript(userScript)
userContentController.add(scriptMessageHandler, name: scriptHandlerName)
}
}
Uso
Init WKWebView
let config = WKWebViewConfiguration()
config.addScript(script: WKUserScript.getUrlScript(scriptName: getUrlAtDocumentStartScript), scriptHandlerName:getUrlAtDocumentStartScript, scriptMessageHandler: self, injectionTime: .atDocumentStart)
config.addScript(script: WKUserScript.getUrlScript(scriptName: getUrlAtDocumentEndScript), scriptHandlerName:getUrlAtDocumentEndScript, scriptMessageHandler: self, injectionTime: .atDocumentEnd)
webView = WKWebView(frame: UIScreen.main.bounds, configuration: config)
webView.navigationDelegate = self
Atrapar eventos
extension ViewController: WKScriptMessageHandler {
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
switch message.name {
case getUrlAtDocumentStartScript:
print("start: /(message.body)")
case getUrlAtDocumentEndScript:
print("end: /(message.body)")
default:
break;
}
}
}
Ejemplo de código completo
import UIKit
import WebKit
class ViewController: UIViewController, WKNavigationDelegate {
var webView = WKWebView()
let getUrlAtDocumentStartScript = "GetUrlAtDocumentStart"
let getUrlAtDocumentEndScript = "GetUrlAtDocumentEnd"
override func viewDidLoad() {
super.viewDidLoad()
let config = WKWebViewConfiguration()
config.addScript(script: WKUserScript.getUrlScript(scriptName: getUrlAtDocumentStartScript), scriptHandlerName:getUrlAtDocumentStartScript, scriptMessageHandler: self, injectionTime: .atDocumentStart)
config.addScript(script: WKUserScript.getUrlScript(scriptName: getUrlAtDocumentEndScript), scriptHandlerName:getUrlAtDocumentEndScript, scriptMessageHandler: self, injectionTime: .atDocumentEnd)
webView = WKWebView(frame: UIScreen.main.bounds, configuration: config)
webView.navigationDelegate = self
view.addSubview(webView)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
webView.loadUrl(string: "http://apple.com")
}
}
extension ViewController: WKScriptMessageHandler {
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
switch message.name {
case getUrlAtDocumentStartScript:
print("start: /(message.body)")
case getUrlAtDocumentEndScript:
print("end: /(message.body)")
default:
break;
}
}
}
extension WKWebView {
func loadUrl(string: String) {
if let url = URL(string: string) {
load(URLRequest(url: url))
}
}
}
extension WKUserScript {
class func getUrlScript(scriptName: String) -> String {
return "webkit.messageHandlers./(scriptName).postMessage(document.URL)"
}
}
extension WKWebViewConfiguration {
func addScript(script: String, scriptHandlerName:String, scriptMessageHandler: WKScriptMessageHandler, injectionTime:WKUserScriptInjectionTime) {
let userScript = WKUserScript(source: script, injectionTime: injectionTime, forMainFrameOnly: false)
userContentController.addUserScript(userScript)
userContentController.add(scriptMessageHandler, name: scriptHandlerName)
}
}
Info.plist
agregue su configuración de seguridad de transporte Info.plist
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
Resultado
Recursos
(Archivé un Radar para esto poco después de hacer la pregunta aquí).
Recientemente se agregó un nuevo método hace unos días (gracias jcesarmobile por señalarlo):
Agregar
-[WKWebView evaluateJavaScript:completionHandler:]
trac.webkit.org/changeset/169765
El método está disponible en iOS 8 beta 3 y posteriores. Aquí está la nueva firma de método:
/* @abstract Evaluates the given JavaScript string.
@param javaScriptString The JavaScript string to evaluate.
@param completionHandler A block to invoke when script evaluation completes
or fails.
@discussion The completionHandler is passed the result of the script evaluation
or an error.
*/
- (void)evaluateJavaScript:(NSString *)javaScriptString
completionHandler:(void (^)(id, NSError *))completionHandler;
Los documentos están disponibles aquí: https://developer.apple.com/documentation/webkit/wkwebview/1415017-evaluatejavascript .
Acabo de empezar a cavar alrededor de la API WKWebView, así que esta podría no ser la mejor manera, pero creo que podrías hacerlo con el siguiente código:
NSString *scriptSource = @"console.log(''Hi this is in JavaScript'');";
WKUserScript *userScript = [[WKUserScript alloc]
initWithSource:scriptSource
injectionTime:WKUserScriptInjectionTimeAtDocumentStart
forMainFrameOnly:YES];
[myWKController addUserScript:userScript];
(de la charla de WWDC''14)
Aquí hay algo que me funciona:
Cree una extensión en WKWebView que defina un método ''runJavaScriptInMainFrame:''. En el método de extensión, use NSInvocationOperation para llamar al método ''_runJavaScriptInMainFrame:'' no documentado.
extension WKWebView {
func runJavaScriptInMainFrame(#scriptString: NSString) -> Void {
let selector : Selector = "_runJavaScriptInMainFrame:"
let invocation = NSInvocationOperation(target: self, selector: selector, object: scriptString)
NSOperationQueue.mainQueue().addOperation(invocation)
}
}
Para usar, llama:
webview.runJavacriptInMainFrame:(scriptString: "some javascript code")
Gracias a Larsaronen por proporcionar el enlace a la API privada para WKWebView.
Puede que no sea un método ideal, pero dependiendo de su caso de uso, puede volver a cargar WKWebView después de haber infectado el script del usuario:
NSString *scriptSource = @"alert(''WKWebView JS Call!'')";
WKUserScript *userScript = [[WKUserScript alloc] initWithSource:scriptSource
injectionTime:WKUserScriptInjectionTimeAtDocumentEnd
forMainFrameOnly:YES];
[wkWebView.configuration.userContentController addUserScript:userScript];
[wkWebView reload];
Se rumorea que esto es un error porque hay una función privada similar (?) A lo que está disponible públicamente en el UIWebView para evaluar javascript desde obj-C.