【发布时间】:2014-11-10 19:38:42
【问题描述】:
是否可以使用 WKWebView 在 JavaScript 和 Swift/Obj-C 本机代码之间进行同步通信?
这些是我尝试过但失败的方法。
方法 1:使用脚本处理程序
WKWebView接收JS消息的新方式是使用userContentController:didReceiveScriptMessage:的委托方法window.webkit.messageHandlers.myMsgHandler.postMessage('What's the meaning of life, native code?')从JS调用
这种方式的问题是,在执行原生委托方法的过程中,JS执行没有被阻塞,所以我们不能立即调用webView.evaluateJavaScript("something = 42", completionHandler: nil)返回值。
示例 (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
示例(斯威夫特)
func userContentController(userContentController: WKUserContentController, didReceiveScriptMessage message: WKScriptMessage) {
webView.evaluateJavaScript("something = 42", completionHandler: nil)
}
方法 2:使用自定义 URL 方案
在 JS 中,使用 window.location = "js://webView?hello=world" 进行重定向会调用原生的 WKNavigationDelegate 方法,其中可以提取 URL 查询参数。然而,与 UIWebView 不同,委托方法不会阻塞 JS 的执行,因此立即调用 evaluateJavaScript 将值传回 JS 在这里也不起作用。
示例 (JavaScript)
var something;
function getSomething() {
window.location = "js://webView?question=meaning" // Execution NOT blocking here either :(
return something;
}
getSomething(); // Returns undefined
示例(斯威夫特)
func webView(webView: WKWebView, decidePolicyForNavigationAction navigationAction: WKNavigationAction, decisionHandler decisionHandler: (WKNavigationActionPolicy) -> Void) {
webView.evaluateJavaScript("something = 42", completionHandler: nil)
decisionHandler(WKNavigationActionPolicy.Allow)
}
方法 3:使用自定义 URL 方案和 IFRAME
这种方法仅在分配window.location 的方式上有所不同。不是直接分配它,而是使用空iframe 的src 属性。
示例 (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();
尽管如此,这也不是一个解决方案,它调用与方法 2 相同的本机方法,它不是同步的。
附录:如何使用旧的 UIWebView 实现此目的
示例 (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
示例(斯威夫特)
func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool {
webView.stringByEvaluatingJavaScriptFromString("something = 42")
}
【问题讨论】:
-
利用异步特性不是更好的方法(就用户体验而言)吗?例如。在您向 webkit 发布的消息中,提供有关应该从 webkit 发布操作“结果”的位置的信息。例如。 WebKit 可以使用 stringByEvaluatingJavaScriptFromString 调用 gotSomething() javascript 方法?
-
假设您想询问本机代码是否安装了应用程序。如果你使用同步调用,你可以做一些简单的事情,比如
var isTwitterInstalled = isInstalled('twitter');。如果您只有异步调用,则必须将其拆分为两个函数;一个从 JavaScript 调用,另一个从本机代码调用,传递结果。 -
是的,但优点是,如果执行您试图在 iOS 世界中运行的代码需要任何时间,您的 JavaScript 根本不会等待它,这使得用户界面响应更快。这种方式类似于Node。
-
我在这里遇到的问题是,似乎不可能调用 JavaScript 并使用该调用的结果 * 来响应 UI/框架请求。例如,打印或复印。我尝试通过完成处理程序进行同步都导致了死锁。
-
我注意到您的示例代码使用了局部变量,但您评估的 JS 可能不在同一范围内运行。您是否尝试过使用
window上的变量?