前言
了解本文之前需要准备 JS 和 WebView 中的一些基础知识,需要知道 JS 的基本语法和 WebView 中调用 JS 的常用接口。
iOS 实现 JS 和 Native 交互的 WebView 有 UIWebView 和 WKWebView。
UIWebView 通过 KVC 拿到 UIWebView 的JSContext,通过 JSContext 实现交互 而 WKWebView 有了新特性 MessageHandler 来实现 JS 调用 Native 方法。
从实现思路是来讲,UIWebView 和 WKWebView 是一样的。 所以,本文只介绍 WKWebView 上 JS 和 Native 的交互思路,UIWebView 有需求的可以模仿实现。
JS 和 Native 交互常用的场景
常用的分为下面几种场景:
- H5 获取本地用户信息(这种比较简单,只需要本地注入JS就行了,思路有三种下面介绍)
- H5 传递信息给 Native,Native 调用分享(这种属于JS调用 Native)
- Native 告诉 H5 分享结果(这种属于原生调用JS)
1)H5 获取本地用户信息
a.通过WKUserContentController
注入 JS 实现
现有用户信息格式如下,需要注入到JS,供H5调用:
let userInfo = ["name": "wb", "sex": "male", "phone": "12333434"]
📱 1.a1 Native 注入 JS 变量实现如下
let userContent = WKUserContentController.init() let userInfo = ["name": "wb", "sex": "male", "phone": "12333434"] for key in userInfo.keys { let script = WKUserScript.init(source: "var \(key) = \"\(userInfo[key]!)\"", injectionTime: .atDocumentStart, forMainFrameOnly: true) userContent.addUserScript(script) } let config = WKWebViewConfiguration.init() config.userContentController = userContent let wkWebView: WKWebView = WKWebView.init(frame: UIScreen.main.bounds, configuration: config) wkWebView.navigationDelegate = self wkWebView.uiDelegate = self view.addSubview(wkWebView) view.insertSubview(wkWebView, at: 0) wkWebView.load(URLRequest.init(url: URL.init(string: "http://192.168.2.1/js.html")!))
通过遍历用户信息的 key,把 key 作为变量,value 作为字符串值,注入到 JS 上下文中。
🖥 在H5中实现调用如下
<!DOCTYPE html> <html> <head> <title>js Bridge demo</title> <script type="text/javascript"> function btnClick() { try { alert(name) alert(sex) alert(phone) } catch (err) { alert(err) } } </script> </head> <body> <h1>js demo test</h1> <p style="text-align: center;"> <button type="button" onclick="btnClick()" style="font-size: 100px;">test JS</button> </p> </body> </html>
📱 1.a2 Native 注入 JS 对象实现如下
let userContent = WKUserContentController.init() let userInfo = ["name": "wb", "sex": "male", "phone": "12333434"] let jsonData = try? JSONSerialization.data(withJSONObject: userInfo, options: .prettyPrinted) let jsonText = String.init(data: jsonData!, encoding: String.Encoding.utf8) let script = WKUserScript.init(source: "var userInfo = \(jsonText!)", injectionTime: .atDocumentStart, forMainFrameOnly: true) userContent.addUserScript(script) let config = WKWebViewConfiguration.init() config.userContentController = userContent let wkWebView: WKWebView = WKWebView.init(frame: UIScreen.main.bounds, configuration: config) wkWebView.navigationDelegate = self wkWebView.uiDelegate = self view.addSubview(wkWebView) view.insertSubview(wkWebView, at: 0) wkWebView.load(URLRequest.init(url: URL.init(string: "http://192.168.2.1/js.html")!))
通过把用户信息字典转化成JSON,作为对象赋值给用户信息,注入JS上下文中。
🖥 在H5中实现调用如下
<!DOCTYPE html> <html> <head> <title>js Bridge demo</title> <script type="text/javascript"> function btnClick() { try { alert(JSON.stringify(userInfo)) } catch (err) { alert(err) } } </script> </head> <body> <h1>js demo test</h1> <p style="text-align: center;"> <button type="button" onclick="btnClick()" style="font-size: 100px;">test JS</button> </p> </body> </html>
📱 1.a3 Native 注入 JS 函数实现如下
let userContent = WKUserContentController.init() let userInfo = ["name": "wb", "sex": "male", "phone": "12333434"] let jsonData = try? JSONSerialization.data(withJSONObject: userInfo, options: .prettyPrinted) let jsonText = String.init(data: jsonData!, encoding: String.Encoding.utf8) let script = WKUserScript.init(source: "var iOSApp = {\"getUserInfo\":function(){return \(jsonText!)}}", injectionTime: .atDocumentStart, forMainFrameOnly: true) userContent.addUserScript(script) let config = WKWebViewConfiguration.init() config.userContentController = userContent let wkWebView: WKWebView = WKWebView.init(frame: UIScreen.main.bounds, configuration: config) wkWebView.navigationDelegate = self wkWebView.uiDelegate = self view.addSubview(wkWebView) view.insertSubview(wkWebView, at: 0) wkWebView.load(URLRequest.init(url: URL.init(string: "http://192.168.2.1/js.html")!))
通过封装getUserInfo
匿名函数,执行函数返回我们的对象,生成全局对象iOSApp,调用iOSApp.getUserInfo()
。
这样写的好处是,我们的H5在调用函数的时候,可以很容易知道哪些是 Native 注入,防止和本地造成冲突,便于理解。
🖥 在H5中实现调用如下
<!DOCTYPE html> <html> <head> <title>js Bridge demo</title> <script type="text/javascript"> function btnClick() { try { alert(JSON.stringify(iOSApp.getUserInfo())) } catch (err) { alert(err) } } </script> </head> <body> <h1>js demo test</h1> <p style="text-align: center;"> <button type="button" onclick="btnClick()" style="font-size: 100px;">test JS</button> </p> </body> </html>
以上讲了三种方式实现用户信息的传递,都是通过WKUserContentController
注入JS实现的,实际上我也可以通过网页视图的evaluateJavaScript
方法实现注入。
b.通过 evaluateJavaScript
方法实现
同样的 WebView 中的调用 H5,提供了evaluateJavaScript
接口,此接口既可以执行JS 函数回调结果,也可以注入 JS。
📱 使用接口实现JS函数的注入
let userContent = WKUserContentController.init() let config = WKWebViewConfiguration.init() config.userContentController = userContent let wkWebView: WKWebView = WKWebView.init(frame: UIScreen.main.bounds, configuration: config) wkWebView.navigationDelegate = self wkWebView.uiDelegate = self view.addSubview(wkWebView) view.insertSubview(wkWebView, at: 0) wkWebView.load(URLRequest.init(url: URL.init(string: "http://192.168.2.1/js.html")!)) ... //代理方法加载完成 func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { let userInfo = ["name": "wb", "sex": "male", "phone": "12333434"] let jsonData = try? JSONSerialization.data(withJSONObject: userInfo, options: .prettyPrinted) let jsonText = String.init(data: jsonData!, encoding: String.Encoding.utf8) webView.evaluateJavaScript("var iOSApp = {\"getUserInfo\":function(){return \(jsonText!)}}", completionHandler: nil) }
在WebView中加载完成之后,使用evaluateJavaScript
实现了JS函数的注入,H5实现调用正常。
2)H5 传递信息给 Native,Native 调用分享
很多时候 H5 需要传递信息给 Native,Native 再执行相应的逻辑。
🖥 H5实现代码如下
<!DOCTYPE html> <html> <head> <title>js Bridge demo</title> <meta charset="utf-8"> <script type="text/javascript"> function btnClick() { try { window.webkit.messageHandlers.shareAction.postMessage({"title":"分享", "content":"内容", "url":"链接"}) } catch (err) { alert(err) } } </script> </head> <body> <h1>js demo test</h1> <p style="text-align: center;"> <button type="button" onclick="btnClick()" style="font-size: 100px;">test JS</button> </p> </body> </html>
H5 将分享内容通过
window.webkit.messageHandlers.shareAction.postMessage
交给 Native
📱 Native 实现代码如下
let userContent = WKUserContentController.init() userContent.add(self, name: "shareAction") let config = WKWebViewConfiguration.init() config.userContentController = userContent let wkWebView: WKWebView = WKWebView.init(frame: UIScreen.main.bounds, configuration: config) wkWebView.navigationDelegate = self wkWebView.uiDelegate = self view.addSubview(wkWebView) view.insertSubview(wkWebView, at: 0) wkWebView.load(URLRequest.init(url: URL.init(string: "http://192.168.2.1/js.html")!)) ... //代理方法,window.webkit.messageHandlers.xxx.postMessage(xxx)实现发送到这里 func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { print(message.body) print(message.name) print(message.frameInfo.request) if message.name == "shareAction" { let list = message.body as! [String: String] print(list["title"]!) print(list["content"]!) print(list["url"]!) } }
userContent.add(self, name: "shareAction")
本地添加shareAction
的接口声明,当JS调用shareAction
回调代理方法,实现参数捕获(WKScriptMessage)。 这样 Native 就得到了分享的传参了,然后可以调用本地 SDK 实现分享的逻辑了。