type - wkwebview navigation swift
Comunicación nativa sÃncrona de JavaScript a WKWebView (4)
¿Es posible la comunicación sincrónica entre JavaScript y el código nativo de Swift / Obj-C usando WKWebView?
Estos son los enfoques que he probado y han fallado.
Enfoque 1: uso de controladores de script
La nueva forma de
WKWebView
de recibir mensajes JS es mediante el uso del método delegado
userContentController:didReceiveScriptMessage:
invocado desde JS por
window.webkit.messageHandlers.myMsgHandler.postMessage(''What''s the meaning of life, native code?'')
El problema con Este enfoque es que durante la ejecución del método delegado nativo, la ejecución de JS no se bloquea, por lo que no podemos devolver un valor invocando inmediatamente
webView.evaluateJavaScript("something = 42", completionHandler: nil)
.
Ejemplo (JavaScript)
var something;
function getSomething() {
window.webkit.messageHandlers.myMsgHandler.postMessage("What''s the meaning of life, native code?"); // Execution NOT blocking here :(
return something;
}
getSomething(); // Returns undefined
Ejemplo (Swift)
func userContentController(userContentController: WKUserContentController, didReceiveScriptMessage message: WKScriptMessage) {
webView.evaluateJavaScript("something = 42", completionHandler: nil)
}
Enfoque 2: uso de un esquema de URL personalizado
En JS, la redirección utilizando
window.location = "js://webView?hello=world"
invoca los métodos nativos
WKNavigationDelegate
, donde se pueden extraer los parámetros de consulta de URL.
Sin embargo, a
diferencia de UIWebView
, el método delegado no está bloqueando la ejecución de JS, por lo que invocar de inmediato EvalJavaScript para pasar un valor a JS tampoco funciona aquí.
Ejemplo (JavaScript)
var something;
function getSomething() {
window.location = "js://webView?question=meaning" // Execution NOT blocking here either :(
return something;
}
getSomething(); // Returns undefined
Ejemplo (Swift)
func webView(webView: WKWebView, decidePolicyForNavigationAction navigationAction: WKNavigationAction, decisionHandler decisionHandler: (WKNavigationActionPolicy) -> Void) {
webView.evaluateJavaScript("something = 42", completionHandler: nil)
decisionHandler(WKNavigationActionPolicy.Allow)
}
Enfoque 3: uso de un esquema de URL personalizado y un IFRAME
Este enfoque solo difiere en la forma en que se asigna
window.location
.
En lugar de asignarlo directamente, se utiliza el atributo
src
de un
iframe
vacío.
Ejemplo (JavaScript)
var something;
function getSomething() {
var iframe = document.createElement("IFRAME");
iframe.setAttribute("src", "js://webView?hello=world");
document.documentElement.appendChild(iframe); // Execution NOT blocking here either :(
iframe.parentNode.removeChild(iframe);
iframe = null;
return something;
}
getSomething();
Sin embargo, esto tampoco es una solución, invoca el mismo método nativo que el Enfoque 2, que no es sincrónico.
Apéndice: Cómo lograr esto con el antiguo UIWebView
Ejemplo (JavaScript)
var something;
function getSomething() {
// window.location = "js://webView?question=meaning" // Execution is NOT blocking if you use this.
// Execution IS BLOCKING if you use this.
var iframe = document.createElement("IFRAME");
iframe.setAttribute("src", "js://webView?question=meaning");
document.documentElement.appendChild(iframe);
iframe.parentNode.removeChild(iframe);
iframe = null;
return something;
}
getSomething(); // Returns 42
Ejemplo (Swift)
func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool {
webView.stringByEvaluatingJavaScriptFromString("something = 42")
}
Encontré un truco para hacer comunicación sincrónica pero aún no lo he probado: https://.com/a/49474323/2870783
Editar: Básicamente, puede usar el indicador JS () para transportar su carga desde el lado js al lado nativo. En el WKWebView nativo tendrá que interceptar la llamada de solicitud y decidir si es una llamada normal o si es una llamada jsbridge. Luego puede devolver su resultado como devolución de llamada a la llamada de solicitud. Debido a que la llamada rápida se implementa de tal manera que espera la entrada del usuario, su comunicación nativa de JavaScript será síncrona. La desventaja es que solo puede comunicarse a través de cadenas.
Estaba enfrentando un problema similar, lo resolví almacenando devoluciones de llamadas prometedoras.
El js que carga en su vista web a través de WKUserContentController :: addUserScript
var webClient = {
id: 1,
handlers: {},
};
webClient.onMessageReceive = (handle, error, data) => {
if (error && webClient.handlers[handle].reject) {
webClient.handlers[handle].reject(data);
} else if (webClient.handlers[handle].resolve){
webClient.handlers[handle].resolve(data);
}
delete webClient.handlers[handle];
};
webClient.sendMessage = (data) => {
return new Promise((resolve, reject) => {
const handle = ''m'' + webClient.id++;
webClient.handlers[handle] = { resolve, reject };
window.webkit.messageHandlers.<message_handler_name>.postMessage({data: data, id: handle});
});
}
Realizar solicitud Js como
webClient.sendMessage(<request_data>).then((response) => {
...
}).catch((reason) => {
...
});
Recibir solicitud en userContentController: didReceiveScriptMessage
Llame a EvalJavaScript con webClient.onMessageReceive (handle, error, response_data).
No, no creo que sea posible debido a la arquitectura multiproceso de WKWebView. WKWebView se ejecuta en el mismo proceso que su aplicación, pero se comunica con WebKit que se ejecuta en su propio proceso ( Presentación de la API Modern WebKit ). El código JavaScript se ejecutará en el proceso de WebKit. Esencialmente, usted está pidiendo tener comunicación sincrónica entre dos procesos diferentes que va en contra de su diseño.
También investigué este problema y fallé como tú. Para solucionarlo, debe pasar una función de JavaScript como devolución de llamada. La función nativa necesita evaluar la función de devolución de llamada para devolver el resultado. En realidad, esta es la forma de JavaScript, porque JavaScript nunca espera. Bloquear el hilo de JavaScript puede causar ANR, es muy malo.
He creado un proyecto llamado XWebView que puede establecer un puente entre nativo y JavaScript. Ofrece una API de estilo vinculante para llamar nativo desde JavaScript y viceversa. Hay una aplicación de sample .