JS和OC的交互這是個永恆話題,使用場景也是越來越多,如今一些reactnative、vue框架等,都是在重點結合原生與H5的混合使用。
那么,如何快捷方便的使用兩者交互是一個很重要的關鍵點。
1、傳統上的交互使用:
- OC調用JS:
webView對象通過調用stringByEvaluatingJavaScriptFromString這個方法執行一段JS代碼實現交互。
如:
OC代碼:
[self.webView stringByEvaluatingJavaScriptFromString:@"ocCallJS({'name':'xiaoxiao'})"];
JS代碼:
</script> function ocCallJS(data) { var obj = eval(data); alert(obj.name); } <script>
這種方式對一些簡單場景比較適用,也很方便。
- JS調用OC:
webView攔截url鏈接,獲取內容,再處理邏輯
如:
-(BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;
實現以上webView的代理方法,當webView每次開始加載URL時會進入這個方法,我們便可以在這個方法實現JS調用OC。
舉例大概如下:
JS代碼:
OC代碼:
(圖片來源於網絡)
這種JS調用OC的方法的缺點十分明顯,需要繁瑣地解釋字符串得到相應的方法名和傳值,且調用的方法也不能傳遞返回值;
但優點是:不需要等待頁面加載完才觸發,當相應的代碼被運行就能調用OC的方法(相比 JavaScriptCore而言,下文會講到)。
2、蘋果推薦的框架--JavaScriptCore
JavaScriptCore是蘋果在iOS7時新推出用以實現JS和iOS代碼交互的框架,十分簡單高效。
使用這種,需要導入JavaScriptCore.framework框架。
oc調用js時:
-(void)webViewDidFinishLoad:(UIWebView *)webView { //網頁加載完成調用此方法 //首先創建JSContext 對象(此處通過當前webView的鍵獲取到jscontext) JSContext *context=[webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"]; NSString *alertJS=@"alert('test js OC')"; //准備執行的js代碼 [context evaluateScript:alertJS];//通過oc方法調用js的alert }
這句話的意思,是在webview加載結束,注入一段js代碼,和傳統方式有異曲同工之處。
js調用oc時:
這個是這個框架的優勢重點。這里基本解決了兩者的調用,並且能夠實現,調用之后的回調處理。
舉例:使用block方式比較簡單,也是我比價推薦的一種,但是要注意防止循環引用問題的發生。
HTML文件按鈕代碼
<button onclick="myAction(str);" style="">點擊按鈕返回上一個頁面</button>
OC中代碼
- (void)webViewDidFinishLoad:(UIWebView *)webView方法中對block塊進行代碼實現. __weak typeof(self)temp = self; self.context[@"myAction"] = ^(NSString *str){ //如果有參數,就是str [temp.navigationController popViewControllerAnimated:YES]; };
但!!!這里有個坑,就是oc調用js,必須是html加載完成之后(webViewDidFinishLoad)才可以。
3、WKScriptMessageHandler
注意:使用時必須iOS8+以及WKWebView(其實WKWebView並不支持方法二),如果用wk,我們可以用WKWebView的WKScriptMessageHandler 來實現交互。
第一步:初始化WKWebView,調用addScriptMessageHandler:name:方法,name為js中的方法名,如scan:
- (void)setupWKWebView{ WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init]; configuration.userContentController = [[WKUserContentController alloc] init]; [configuration.userContentController addScriptMessageHandler:self name:@"funcname"]; WKWebView *webView = [[WKWebView alloc] initWithFrame:self.view.frame configuration:configuration]; webView.UIDelegate = self; }
oc調用js:
//OC調用JS NSString *JSResult = [NSString stringWithFormat:@"shareResult('%@','%@','%@')",title,content,url]; [self.webView evaluateJavaScript:JSResult completionHandler:^(id _Nullable result, NSError * _Nullable error) { NSLog(@"%@", error); }];
js調用oc:
h5:
func(){ window.webkit.messageHandlers.funcname.postMessage()
}
// 注意:這里funcname是方法名,必須和oc里定義的一致
oc:在WKScriptMessageHandler代理方法,當js調用scan方法時,會回調此代理方法:
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{ if ([message.name isEqualToString:@"funcname"]) {
//調用原生掃碼
}
}
4、優秀的第三方框架--WebViewJavascriptBridge
到了這里,就是本文的重點了,介紹一下WebViewJavascriptBridge
WebViewJavascriptBridge同時支持UIWeView和WKWebView,無論是JS調用OC還是OC調用JS,都可以正常傳值和返回值;而且在頁面加載時只要JS代碼被運行就可以進行交互,上面遇到的缺點和坑在這里都被掩埋的,所以是現在處理交互的主流做法。
網上我找的資料,很多也是從官方demo衍生出來的,本文也不例外,不同的是,我會在代碼里,詳細的添加各種注釋,保證大家能快速的理解。
使用介紹:
1、准備文件
2、js代碼截取片段
解釋:這段代碼是必須的,申明交互直接拷貝即可,處理交互部分,需要改動,關鍵就是和oc端協商的方法名,以及js內部需要的處理邏輯。
<!-- 申明交互 --> function setupWebViewJavascriptBridge(callback) { if (window.WebViewJavascriptBridge) { return callback(WebViewJavascriptBridge); } if (window.WVJBCallbacks) { return window.WVJBCallbacks.push(callback); } window.WVJBCallbacks = [callback]; var WVJBIframe = document.createElement('iframe'); WVJBIframe.style.display = 'none'; WVJBIframe.src = 'https://__bridge_loaded__'; document.documentElement.appendChild(WVJBIframe); setTimeout(function() { document.documentElement.removeChild(WVJBIframe) }, 0) } <!-- 處理交互 方法名要和ios內定義的對應--> setupWebViewJavascriptBridge(function(bridge) { <!--處理 oc 調用 js --> bridge.registerHandler('registerAction', function(data, responseCallback) { //處理oc給的傳參 alert('oc請求js 傳值參數是:'+data) var responseData = { 'result':'handle success' } //處理完,回調傳值給oc responseCallback(responseData) }) var callbackButton = document.getElementById('buttons').appendChild(document.createElement('button')) callbackButton.innerHTML = '點擊我,我會調用oc的方法' callbackButton.onclick = function(e) { e.preventDefault() <!--處理 js 調用 oc --> bridge.callHandler('loginAction', {'userId':'zhangsan','name': '章三'}, function(response) { //處理oc過來的回調 alert('收到oc過來的回調:'+response) }) } })
3、OC代碼
- pod導入框架
pod 'WebViewJavascriptBridge'
- import頭部
#import "WebViewJavascriptBridge.h"
- viewDidload
//初始化 WebViewJavascriptBridge if (_bridge) { return; } [WebViewJavascriptBridge enableLogging]; _bridge = [WebViewJavascriptBridge bridgeForWebView:webView]; [_bridge setWebViewDelegate:self]; //請求加載html,注意:這里h5加載完,會自動執行一個調用oc的方法 [self loadExamplePage:webView]; //申明js調用oc方法的處理事件,這里寫了后,h5那邊只要請求了,oc內部就會響應 [self JS2OC]; //模擬操作:2秒后,oc會調用js的方法 //注意:這里厲害的是,我們不需要等待html加載完成,就能處理oc的請求事件;此外,webview的request 也可以在這個請求后面執行(可以把上面的[self loadExamplePage:webView]放到[self OC2JS]后面執行,結果是一樣的) dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [self OC2JS]; });
- JS 調用 OC
-(void)JS2OC{ /* 含義:JS調用OC @param registerHandler 要注冊的事件名稱(比如這里我們為loginAction) @param handel 回調block函數 當后台觸發這個事件的時候會執行block里面的代碼 */ [_bridge registerHandler:@"loginAction" handler:^(id data, WVJBResponseCallback responseCallback) { // data js頁面傳過來的參數 假設這里是用戶名和姓名,字典格式 NSLog(@"JS調用OC,並傳值過來"); // 利用data參數處理自己的邏輯 NSDictionary *dict = (NSDictionary *)data; NSString *str = [NSString stringWithFormat:@"用戶名:%@ 姓名:%@",dict[@"userId"],dict[@"name"]]; [self renderButtons:str]; // responseCallback 給js的回復 responseCallback(@"報告,oc已收到js的請求"); }]; }
- OC 調用 JS
-(void)OC2JS{ /* 含義:OC調用JS @param callHandler 商定的事件名稱,用來調用網頁里面相應的事件實現 @param data id類型,相當於我們函數中的參數,向網頁傳遞函數執行需要的參數 注意,這里callHandler分3種,根據需不需要傳參數和需不需要后台返回執行結果來決定用哪個 */ //[_bridge callHandler:@"registerAction" data:@"我是oc請求js的參數"]; [_bridge callHandler:@"registerAction" data:@"uid:123 pwd:123" responseCallback:^(id responseData) { NSLog(@"oc請求js后接受的回調結果:%@",responseData); }]; }
這里的關鍵點就是:OC和JS商定的方法名要統一,兩端要合作一下。
這里的舉例,我都用到了處理后的回調,大家運行demo的時候注意看日志文件。
源碼下載:點擊這里獲取Demo
以上幾種方法就到這里里,如果需求比較簡單,不一定需要使用最后一種,靈活運用更方便~
enjoy