在iOS中 JS 和原生APP交互有許多種方法
WebViewJavascriptBridge 是我用的比較多的一個庫,但是 WebViewJavascriptBridge 中在使用時最大的一個缺點就是 WebViewJavascriptBridge 使用了一箇中間代理
setWebViewDelegate
必須將 UIWebView、WKWebView 的代理設置爲 WebViewJavascriptBridge 內部的處理類
這就造成了對原始的調用方法有一定的影響
WKWebView
iOS 8 之後,無疑沒有理由不使用 WKWebView
在 JS 端只需要 window.webkit.messageHandler.<method>.postMessage(<body>)即可
在APP端通過 configuration.userContentController.add(_scriptMessageHandler:WKScriptMessageHandler, name:String) 並且實現協議方法即可
在 APP 端只需要 evaluateJavaScript 既可以調用 JS 方法
通過上面三種方法便可以實現大多數的 JS 原生交互
WKUserScript
通過configuration.userContentController.addUserScript 便可以添加一個 WKUserScript 對象,從而可以插入需要額外加入的 JS 代碼
WKScriptMessageHandler
至於 messageHandler 的具體的使用用法,這裏便不做詳細的說明
下面是對 MessageHandler 的一個封裝,所謂的封裝就是懶人的一個高尚的藉口。 說一下大致思路:
創建一個類 WKJSHandler 此類的初始化方法中會對 webView 有一個弱引用,只有擁有了 webView 才能做任何事情
我們無論是使用 regist 或者 call 和 JS 相關的方法,一般都是採用 block 的方法 block 一般最直接的表現就是異步
所以我們需要 WKJSHandler 中存在一個屬性 data 對block 進行持有,從而可以實現異步回調 所以在 WKScriptMessageHandler 協議方法執行的時候,根據name 取出來對應的block進行回調
import UIKit import WebKit open class WKJSHandler: NSObject, WKScriptMessageHandler { public typealias Handler = (Any?) -> Void open weak var webView: WKWebView? public init(webView: WKWebView) { super.init() self.webView = webView let bundle = Bundle.init(for: WKJSHandler.classForCoder()) guard let path = bundle.path(forResource: "injectjs", ofType: nil) else { return } guard let content = try? String.init(contentsOfFile: path) else { return } self.inject(javascript: content, begin: true, main: true) } open var data = [String: Handler]() /// regist a new hander for name /// /// - Parameters: /// - name: handler name /// - handler: callBack open func regist(name: String, handler: @escaping Handler) { self.data[name] = handler self.webView?.configuration.userContentController.removeScriptMessageHandler(forName: name) self.webView?.configuration.userContentController.add(self, name: name) } /// remove message handler /// /// - Parameter name: handler name open func remove(name: String) { self.data[name] = nil self.webView?.configuration.userContentController.removeScriptMessageHandler(forName: name) } /// evaluateJavaScript a javascript /// /// - Parameters: /// - javascript: javascript code /// - callBack: result of evalueteJavaScript open func runJS(javascript: String, result: ((Any?, Error?) -> Void)?) { self.webView?.evaluateJavaScript(javascript, completionHandler: result) } /// inject javascript code /// /// - Parameters: /// - javascript: javascript code /// - begin: When the script should be injected. /// - main: forMainFrameOnly Whether the script should be injected into all frames or just the main frame. open func inject(javascript: String, begin: Bool, main: Bool) { let newScript = WKUserScript.init(source: javascript, injectionTime: begin ? .atDocumentStart : .atDocumentEnd, forMainFrameOnly: main) self.webView?.configuration.userContentController.addUserScript(newScript) } /// delegate method public func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { let name = message.name guard let handler = self.data[name] else { return } handler(message.body) } deinit { for t in self.data { self.remove(name: t.key) } self.data = [String: Handler]() self.webView?.configuration.userContentController.removeAllUserScripts() }
有了此層的封裝,在使用的時候就會顯得非常的方便
self.webView = WKWebView.init(frame: self.view.bounds) self.jsHandler = WKJSHandler.init(webView: self.webView) /// 註冊一個事件,當 self.jsHandler.regist(name: "kkalert") { [weak self] (data) in if let title = data as? String { self?.alert(title: title) } }
上面的源碼可以在 https://github.com/TieShanWang/WKJSHandler 上面找到 當然也可以直接使用 cocoapod 使用 pod 'WKJSHandler' 安裝
當然現在實現了 JS 和 Native 之間的直接方法的調用,並且實現了參數的傳遞 但是我們並不會滿足於此,因爲在實際的使用中,我們多數時候需要從Native 中獲取一些數據,這個時候使用 postMessage 的方法,便不能夠滿足我們的需求了 通過 postMessage 只能夠調用APP的一個方法,並且傳參,沒有辦法直接回調 JS。
同樣在我們調用 JS 方法後也希望 JS 在處理完畢之後會給我們一個回調
我們的需求是 “回調“
在下一章 將會解決一下回調的問題
MessageHandler 高級用法二:原生調用JS 實現回調