WebView 和 JS 通信一般有两种方式
-
第一种就是 WebViewJavascriptBridge 实现的方式,是通过在 js 新建一个 iframe,iframe 指定一个自定义 scheme 的 url,将该 frame 添加到当前 dom 中。那么就可以分别通过 WKWebView 的
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void)
以及 UIWebView 的
func webView(_ webView: UIWebView, shouldStartLoadWith request: URLRequest, navigationType: UIWebView.NavigationType) -> Bool
拦截到该 iframe 的 url,根据 url 的 scheme 再做具体操作,这样就可以进行通信了,具体实现可以看 WebViewJavascriptBridge 的源码。这种方式的好处就是同时适用 UIWebView、WKWebView,项目中若是想要同时兼容这两个 WebView,适用这种方式比较方便。
-
第二种是直接通过原生 API 进行通信,UIWebView 通过 JSContext,WKWebView 通过 WKScriptMessageHandler。本文只讨论 WKWebView 相关
1. WKWebView 调用 JS
WKWebView 调用 js 主要通过以下方法
/* @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.
*/
open func evaluateJavaScript(_ javaScriptString: String, completionHandler: ((Any?, Error?) -> Void)? = nil)
如以下的调用就是将 Object 以及 String 当做参数传递给 js 中的 window.bridge.receiveMessage()
方法
wkWebview.evaluateJavaScript("window.bridge.receiveMessage({'key': 'value'});", completionHandler: nil)
wkWebview.evaluateJavaScript("window.bridge.receiveMessage('message');", completionHandler: nil)
2. JS 调用 WKWebView
JS 调用 WKWebView 主要通过 JS 使用 window.webkit.messageHandlers.name.postMessage(messageBody)
方法
上面的 name
是指什么呢,name
表示在 WKWebView 中添加过的 scriptMessageHandler 的 name,scriptMessageHandler 类似一个 delegate,处理 WKWebView 接收到的 JS 的 message (即上述方法中的 messageBody),每个 scriptMessageHandler 对应一个 name
比如以下代码
// swift
wkWebview.configuration.userContentController.add(self, name: "bridge")
通过上述代码添加了一个 name 为 bridge
的 scriptMessageHandler 后,我们可以通过以下方式像 WKWebView 发送 message
// js
window.webkit.messageHandlers.bridge.postMessage({
"key": "value"
});
WKWebView 怎么接受这些 message 呢
通过实现协议 WKScriptMessageHandler 的方法接受 message
extension JustBridge: WKScriptMessageHandler {
public func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
// body: ["key": "value"]
guard let body = message.body as? [String: Any] else { return }
}
3. JSBridge 的实现
知道了 WKWebView 和 JS 间互相通信的方式,哪么我们就可以以此为基础封装一个 JSBridge
JustBridge 是我完成的一个适用于 WKWebView 的 JSBridge
。JustBridge 或在初始化的时候通过以下代码,在 webView 的 atDocumentStart
的时候向其中注入我们桥接的 JavaScript 代码,因此无需我们另外向前端代码添加桥接 JS 代码。
fileprivate func injectBridgeJS() {
let script = WKUserScript(source: JustBridge.bridge_js, injectionTime: .atDocumentStart, forMainFrameOnly: true)
self.webview.configuration.userContentController.addUserScript(script)
}
使用 JustBridge 唯一需要做的就是在两端通过 register
注册一个操作,在另一端通过 call
调用之前注册的 handler 即可。
- import JustBridge 并且声明一个 JustBridge 对象
import JustBridge
…
var bridge: JustBridge!
- 通过 WKWebView 初始化一个 JustBridge
bridge = JustBridge(with: wkWebView)
- Swift 注册一个 handler,或者请求一个 JS 方法。
// ==========
// swift
// ==========
bridge.register("swiftHandler") { (data, callback) in
print("[js call swift] - data: \(data ?? "nil")\n")
callback("[response from swift] - response data: I'm swift response data")
}
bridge.call("jsHandler", data: data, callback: { responseData in
print(responseData ?? "have no response data")
}, errorCallback: { errorMessage in
// errorMessage 可以为 "HandlerNotExistError" 或 "DataIsInvalidError"
// 分别对应 call 的 handler 不存在的情况以及传入的 data 不为 array, dictinary, string, number 的情况
print(errorMessage)
})
- JS 注册一个 handler,或者请求一个 Swift 方法。
// ==========
// javascript
// ==========
window.bridge.register("jsHandler", function(data, callback) {
console.log("[swift call js] - data: " + JSON.stringify(data));
callback("[response from js] - response data: I'm js response data");
});
window.bridge.call("swiftHandler", "hello world from js", function(responseData) {
console.log(responseData.toString())
}, function(errorMessage) {
console.log("error: " + errorMessage)
});