一、WKWebView
WKWebView 初始化時,有一個參數叫configuration,它是WKWebViewConfiguration類型的參數,而WKWebViewConfiguration有一個屬性叫userContentController,它又是WKUserContentController類型的參數。
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
config.preferences = [[WKPreferences alloc] init];
config.preferences.minimumFontSize = 10;
config.preferences.javaScriptEnabled = YES;
config.preferences.javaScriptCanOpenWindowsAutomatically = NO;
config.userContentController = [[WKUserContentController alloc] init];
config.processPool = [[WKProcessPool alloc] init];
config.userContentController = [WKUserContentController new]; //在創建wkWebView時,需要將被js調用的方法注冊進去,oc與js端對應實現
[config.userContentController addScriptMessageHandler:self name:@"callFunciton"];
WKWebView *wkWebView = [[WKWebView alloc]initWithFrame:self.view.frame configuration:config];
self.wkWebView = wkWebView;
wkWebView.navigationDelegate = self;
wkWebView.UIDelegate = self;
NSURLRequest *request = [[NSURLRequest alloc]initWithURL:self.url];
[wkWebView loadRequest:request];
[self.view addSubview:wkWebView];</pre>
1.JS調用原生MessageHandler
WKUserContentController對象有一個方法
- (void)addScriptMessageHandler:(id <WKScriptMessageHandler>)scriptMessageHandler name:(NSString *)name;
JS調用OC時,這句代碼非常重要
// 在創建wkWebView時,需要將被js調用的方法注冊進去,oc與js端對應實現
[self.wkWebView.configuration.userContentControlle addScriptMessageHandler:self name:@"callFunciton"];
addScriptMessageHandler:name:有兩個參數,第一個參數是userContentController的代理對象,第二個參數是JS里發送postMessage的對象。
所以要使用MessageHandler功能,就必須要實現WKScriptMessageHandler協議。
1.1.實現WKScriptMessageHandler代理方法
當js調用callFunction方法時,會回調此代理方法:
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{ if ([message.name isEqualToString:@"callFunction"]) {
NSString *methodName = message.name;
id params = message.body;
//調用原生掃碼
if ([methodName isEqualToString:@"scan"]) {
}
}
}
Tip: addScriptMessageHandler很容易引起循環引用,導致控制器無法被釋放
- (void)dealloc{
[self.wkWebView.configuration.userContentController removeScriptMessageHandlerForName:@"callFunction"];
}
1.2.JS中使用方法:
window.webkit.messageHandlers.<name>.postMessage(<messageBody>)
//其中<name>,就是上面方法里的第二個參數`name`。
//例如我們調用API的時候第二個參數填@"callFunction",那么在JS里就是:
window.webkit.messageHandlers.callFunction.postMessage(<messageBody>)
//<messageBody>是一個鍵值對,鍵是body,值可以有多種類型的參數,body 的類型:Allowed types are NSNumber, NSString, NSDate, NSArray, NSDictionary, and NSNull messageBody可以為NULL或者其他參數,不能什么都不寫,否則不走代理方法
2.原生調用JS
//1無參數
[self.webView evaluateJavaScript:@"show()" completionHandler:^(id _Nullable response, NSError * _Nullable error) {
//TODO
}];
//2有參數
NSDictionary *dict = @{ @"userAgent": @“userAgentM”, @"custMac":@“custMac”};
NSString *callBackString = [NSString stringWithFormat:@"show(%@)",[self jsonToString:dict]];
[self.webView evaluateJavaScript:callBackString completionHandler:^(id _Nullable obj, NSError * _Nullable error) {
}];
3.WKNavigationDelegate
可以在此通過連接的方式傳遞一些簡單的參數,也是一種H5與原生交互
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
NSString *url = navigationAction.request.URL.absoluteString;
if(![url isEqualToString:self.strURL]) {
// 頁面跳轉
}
decisionHandler(WKNavigationActionPolicyAllow);
}
二、WebViewJavaScriptBridge
WebViewJavaScriptBridge 用于 WKWebView & UIWebView 中 OC 和 JS 交互。
它的基本原理是: 把 OC 的方法注冊到橋梁中,讓 JS 去調用;把 JS 的方法注冊在橋梁中,讓 OC 去調用。
1. 初始化
1.導入頭文件 #import <WebViewJavascriptBridge.h>
2.建立 WebViewJavaScriptBridge 和 WebView 之間的關系
_jsBridge = [WebViewJavascriptBridge bridgeForWebView:_webView];
3.在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){
});
2. 注入OC、JS方法
往橋梁中注入 OC 方法
/* scanClick 是 OC block 的一個別名
* block本身,是JS通過某種方式調用到scanClick的時候,執行的代碼塊
* data,由于OC這端由JS調用,所以data是JS端傳遞過來的數據
* responseCallback OC端的block 執行完畢之后,往JS端傳遞的數據
*/
[_jsBridge registerHandler:@"scanClick" handler:^(id data, WVJBResponseCallback responseCallback) {
NSLog(@"dataFrom JS : %@",data[@"data"]);
responseCallback(@"掃描結果 : www.baidu.com");
}];
往橋梁中注入 JS 函數
/* testJavaScriptFunction: 是注入到橋梁中JS函數的別名,以供OC端調用。
* data: 回調函數的data,既然JS函數由OC調用,所以data是OC端傳遞過來的數據。
* responseCallback: JS調用在被OC調用完畢之后,向OC端傳遞的數據
*/
// 這里主要是注冊 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"});
})
});
3. 調用OC、JS方法
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;
});
4. OC釋放Block
OC中,在當前控制器消失的時候,要記得把注入到橋梁中的 OC block,從橋梁中刪除,否則,可能會出現控制器無法釋放的情況。
[_jsBridge removeHandler:@"scanClick"];
5.示例
1.JS -> OC 的交互
在 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;
})
OC -> JS 的交互
往橋梁中,注入一個修改 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 的背景顏色改成橙色!!!!"];