在 iOS 開發 Hybrid App 的時候,有兩個 WebView 可以選擇。
UIWebView & WKWebView。
這兩個 WebView 控件,可以完全只借助 iOS 自帶的框架進行 OC & JS 交互。
- UIWebView 使用 javaScriptCore.
- WKWebView 使用 WKUserContentController.
UIWebView 原生的交互原理
通過一個 JSContext 獲取 UIWebView 的 JS 執行上下文。
然后通過這個上下文,進行 OC & JS 的雙端交互。
_jsContext = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
_jsContext.exceptionHandler = ^(JSContext *context, JSValue *exception) {
NSLog(@"%@",@"獲取 WebView JS 執行環境失敗了!");
};
WKWebView 原生交互原理
通過 userContentController 把需要觀察的 JS 執行函數注冊起來。
然后通過一個協議方法,將所有注冊過的 JS 函數執行的參數傳遞到此協議方法中。
注冊 需要 觀察的 JS 執行函數
[webView.configuration.userContentController addScriptMessageHandler:self name:@"jsFunc"];
在 JS 中調用這個函數并傳遞參數數據
window.webkit.messageHandlers.jsFunc.postMessage({name : "李四",age : 22});
OC 中遵守 WKScriptMessageHandler
協議。
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
此協議方法里的 WKScriptMessage 有 name & body 兩個屬性。 name 可以用來判斷是哪個 JSFunc 調用了。body 則是 JSFunc 傳遞到 OC 的參數。
WebViewJavaScriptBridge
WebViewJavaScriptBridge 用于 WKWebView & UIWebView 中 OC 和 JS 交互。
它的基本原理是:
把 OC 的方法注冊到橋梁中,讓 JS 去調用。
把 JS 的方法注冊在橋梁中,讓 OC 去調用。
注冊自己,調用它人。
WebViewJavaScriptBridge 使用的基本步驟
- 首先在項目中導入
WebViewJavaScriptBridge 框架
。
pod ‘WebViewJavascriptBridge’
- 導入頭文件
#import <WebViewJavascriptBridge.h>
。 - 建立 WebViewJavaScriptBridge 和 WebView 之間的關系。
_jsBridge = [WebViewJavascriptBridge bridgeForWebView:_webView];
- 在HTML 文件中,復制粘貼這兩段 JS 函數。
function setupWebViewJavascriptBridge(callback) {
if (window.WebViewJavascriptBridge) { return callback(WebViewJavascriptBridge); }
if (window.WVJBCallbacks) { return window.WVJBCallbacks.push(callback); }
window.WVJBCallbacks = [callback]; // 創建一個 WVJBCallbacks 全局屬性數組,并將 callback 插入到數組中。
var WVJBIframe = document.createElement('iframe'); // 創建一個 iframe 元素
WVJBIframe.style.display = 'none'; // 不顯示
WVJBIframe.src = 'wvjbscheme://__BRIDGE_LOADED__'; // 設置 iframe 的 src 屬性
document.documentElement.appendChild(WVJBIframe); // 把 iframe 添加到當前文導航上。
setTimeout(function() { document.documentElement.removeChild(WVJBIframe) }, 0)
}
// 這里主要是注冊 OC 將要調用的 JS 方法。
setupWebViewJavascriptBridge(function(bridge){
});
到此為止,基本的準備工作就做完了。現在需要往橋梁中注入 OC 方法 和 JS 函數了。
往橋梁中注入 OC 方法 和 JS 函數
往橋梁中注入 OC 方法。
[_jsBridge registerHandler:@"scanClick" handler:^(id data, WVJBResponseCallback responseCallback) {
NSLog(@"dataFrom JS : %@",data[@"data"]);
responseCallback(@"掃描結果 : www.baidu.com");
}];
這段代碼的意思:
- scanClick 是 OC block 的一個別名。
- block 本身,是 JS 通過某種方式調用到 scanClick 的時候,執行的代碼塊。
- data ,由于 OC 這端由 JS 調用,所以 data 是 JS 端傳遞過來的數據。
- responseCallback OC 端的 block 執行完畢之后,往 JS 端傳遞的數據。
往橋梁中注入 JS 函數.
OC 方法,在 OC 中注入。JS 的方法所以必然就需要在 JS 中注入的。(好像是廢話)
在 JS 的方法如何注入到橋梁呢?
之前,在準備工作的時候,有兩段 JS 代碼。
需要在第二段 JS 代碼中,注入 JS 的函數。
// 這里主要是注冊 OC 將要調用的 JS 方法。
setupWebViewJavascriptBridge(function(bridge){
// 聲明 OC 需要調用的 JS 方法。
bridge.registerHanlder('testJavaScriptFunction',function(data,responseCallback){
// data 是 OC 傳遞過來的數據.
// responseCallback 是 JS 調用完畢之后傳遞給 OC 的數據
alert("JS 被 OC 調用了.");
responseCallback({data: "js 的數據",from : "JS"});
})
});
這段代碼的意思:
- testJavaScriptFunction 是注入到橋梁中 JS 函數的別名。以供 OC 端調用。
- 回調函數的 data。 既然 JS 函數由 OC 調用,所以 data 是 OC 端傳遞過來的數據。
- responseCallback 。 JS 調用在被 OC 調用完畢之后,向 OC 端傳遞的數據。
基本就是:
OC 端注冊 OC 的方法,OC 端調用 JS 的函數。
JS 端注冊 JS 的函數,JS 端調用 OC 的方法。
場景
JS -> OC 的交互
在 HTML 中,有個按鈕,點擊這個按鈕,修改 NavigationBar 的顏色。
- 在 OC 端,往橋梁注入一個修改 NavigationBar 顏色的 block.
- 在 JS 端,調用這個 block,來間接的達到修改顏色的目的。
首先,在 OC 中,通過 WebViewJavascriptBridge 注冊一個修改 navigationBar 顏色的 Block。
[_jsBridge registerHandler:@"colorClick" handler:^(id data, WVJBResponseCallback responseCallback) {
self.navigationController.navigationBar.barTintColor = [UIColor colorWithRed:arc4random_uniform(256) / 255.0 green:arc4random_uniform(256) / 255.0 blue:arc4random_uniform(256) / 255.0 alpha:1.0];
responseCallback(@"顏色修改完畢!");
}];
然后再 JS 中,通過某種方式去調用這個 OC 的 block。
WebViewJavascriptBridge.callHandler('colorClick',function(dataFromOC) {
alert("JS 調用了 OC 注冊的 colorClick 方法");
document.getElementById("returnValue").value = dataFromOC;
})
這里通過某種方式就是使用 WebViewJavascriptBridge.callHandler('OC 中block 別名',callback)的方式來調用。
OC -> JS 的交互
OC 上有一個UIButton,點擊這兒按鈕,把 HTML body 的顏色修改成橙色。
首先,往橋梁中,注入一個修改 HTML body 顏色的 JSFunction。
// 在這里聲明 OC 需要主動調用 JS 的方法。
setupWebViewJavascriptBridge(function(bridge) {
bridge.registerHandler('changeBGColor',function(data,responseCallback){
// alert('aaaaaa');
document.body.style.backgroundColor = "orange";
document.getElementById("returnValue").value = data;
});
});
然后在 OC 端通過橋梁調用這個 changeBGColor
。
[_jsBridge callHandler:@"changeBGColor" data:@"把 HTML 的背景顏色改成橙色!!!!"];
執行效果:
補充
OC 調用 JS 的三種情況。
// 單純的調用 JSFunction,不往 JS 傳遞參數,也不需要 JSFunction 的返回值。
[_jsBridge callHandler:@"changeBGColor"];
// 調用 JSFunction,并向 JS 傳遞參數,但不需要 JSFunciton 的返回值。
[_jsBridge callHandler:@"changeBGColor" data:@"把 HTML 的背景顏色改成橙色!!!!"];
// 調用 JSFunction ,并向 JS 傳遞參數,也需要 JSFunction 的返回值。
[_jsBridge callHandler:@"changeBGColor" data:@"傳遞給 JS 的參數" responseCallback:^(id responseData) {
NSLog(@"JS 的返回值: %@",responseData);
}];
JS 調用 OC 的三種情況。
// JS 單純的調用 OC 的 block
WebViewJavascriptBridge.callHandler('scanClick');
// JS 調用 OC 的 block,并傳遞 JS 參數
WebViewJavascriptBridge.callHandler('scanClick',"JS 參數");
// JS 調用 OC 的 block,傳遞 JS 參數,并接受 OC 的返回值。
WebViewJavascriptBridge.callHandler('scanClick',{data : "這是 JS 傳遞到 OC 的掃描數據"},function(dataFromOC){
alert("JS 調用了 OC 的掃描方法!");
document.getElementById("returnValue").value = dataFromOC;
});
可以根據實際情況,選擇合適的方法。
關于在 OC 中,往橋梁中注入 block 的注意點。
在當前控制器消失的時候,要記得把注入到橋梁中的 OC block,從橋梁中刪除。
否則,可能會出現控制器無法釋放的情況。
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
[_jsBridge removeHandler:@"scanClick"];
[_jsBridge removeHandler:@"colorClick"];
[_jsBridge removeHandler:@"locationClick"];
[_jsBridge removeHandler:@"shareClick"];
[_jsBridge removeHandler:@"payClick"];
[_jsBridge removeHandler:@"goBackClick"];
}
最后總結:
- UIWebView & JavaScriptCore 等于原生的 JS & OC 交互方案。
- WKWebView & userContentController 等于原生了 JS & OC 交互方案。
- WebViewJavascriptBridge 可以搭配 UIWebView & WKWebView 進行 OC & JS 交互。
- WebViewJavascriptBridge 使用核心,OC 注入 OC 的方法,讓 JS 調用。JS 注入 JS 函數,讓 OC 調用。
- WebViewJavaScriptBridge 使用的需要 4個前提步驟。