現在app 上越來越多需求是通過UIWebView 來展示html 或者 html5的內容, js 和 native OC代碼交互 就非常常見了.
js 調用 native OC代碼
第一種機制
(1)最常用的是 利用 - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType 這個UIWebView 代理方法里攔截 JS 發送的請求,如果是約定的請求 那么就觸發本地該執行的OC方法
我遇到常用的兩種場景:
(1.1)頁面緩沖加載過程中 該shouldStartLoadWithRequest:就可攔截多個請求,查找約定的關鍵字,鎖定約定請求即可觸發本地OC
(1.2)頁面有按鈕類別的焦點區域, 點擊觸發請求,通過 shouldStartLoadWithRequest: 對請求進行判斷 ,觸發本地OC
舉例:html 分享按鈕
<html> <header> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <script type="text/javascript">function shareClick() { loadURL("iOS//:shareAction");//這個鏈接對應 下面iOS方法中同一鏈接的關鍵字 } </script> </header> <body> <h2> 點擊webView上面的按鈕 觸發本地 OC 分享方法 </h2> <button type="button" onclick="shareClick()">按鈕名稱</button> </body> </html>
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType { NSString *requestUrl = request.URL.absoluteString;
DLog(@"請求URL:%@",requestUrl);
//判斷是商品 if(![NSString isEmpty:requestUrl] && [requestUrl hasPrefix:@"hfmall://"]) {//約定 按請求前綴 是"hfmall://"來判斷是商品 甚至可以從請求鏈接里面截取有效參數 如果頻繁處理並且幾個參數或者說有特殊字符,應該考慮用json進行64位編拼在鏈接上傳過來 NSRange range = [requestUrl rangeOfString:@"hfmall://"]; if (range.length > 0) { NSString *shangpinId = [requestUrl substringFromIndex:range.length]; GoodsDetailViewController *detailVC = [[GoodsDetailViewController alloc] init]; detailVC.shangpin_id = shangpinId; [self.navigationController pushViewController:detailVC animated:YES]; return NO; //執行本地代碼 返回NO } }
//點擊UIWebView頁面上分享按鈕 執行本地OC 分享方法
//是文章分享
NSRange range1 = [requestUrl rangeOfString:@"ios//:shareAction"];
if (range1.length != 0) {
[self share]; //執行分享方法
return NO;
}
//TODO:其他情況攔截判斷
return YES;//無有效需要攔截的鏈接 走系統默認方法
}
第二種機制
JavaScripCore
該框架 在 iOS7開始出現,可以實現 js 和 native OC原生交互,添加頭文件#import <JavaScriptCore/JavaScriptCore.h>
[webView loadRequest:request];請求開始或者請求完成后獲取js上下文
(1)js 自由選擇時機調用native端 OC 代碼
html端
<html> <header> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <script type="text/javascript"> function secondClick() { iosHideBottomBar('參數1','參數2');//和 OC代碼中標記紅色背景關鍵名稱一樣 即可識別 }
</script> </header> <body> <h2> js 自由選擇時機調用native端 OC 代碼 </h2> <button type="button" onclick="secondClick()">按鈕名稱</button> </body> </html>
//獲取js上下文
JSContext *context = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
context[@"iosHideBottomBar"] = ^() { // @"iosHideBottomBar"為和js 約定執行的方法名,這樣js 可以自由選擇時機調用native端 OC 代碼
DLog(@"%@",[NSThread currentThread]);//打印當前線程
NSArray *args = [JSContext currentArguments];//回調js 方法iosHideBottomBar傳遞的參數
for (JSValue *jsVal in args) {
DLog(@"%@", jsVal.toString);
}
__block JSContext *contextObject = context;
dispatch_async(dispatch_get_main_queue(), ^{
self.isShowLikeFooter = NO;
[self handleHiddenBottomBar];
NSString *jsString=@"xxxxx('如果需要傳遞返回值')"; //准備執行的js代碼 按約定方法xxxxx回傳返回值
[contextObject evaluateScript:jsString];
});
};
//該閉包是在非主線程中得到的回調,如果需要處理UI要在主線程更新 當前測試環境 為 XCode 8.0 模擬器 5s 8.3 打印結果如下
/**
2016-11-11 17:05:54.447 dailylife[4163:181210] <NSThread: 0x7ff68cc2fd90>{number = 7, name = (null)}
(lldb) po [NSThread currentThread]
<NSThread: 0x7ff68a423150>{number = 1, name = main}
**/
小節:
兩種機制不同:
a. 前者需要 不斷對js 發起的每一條請求進行過濾判斷,再執行早已定義好的OC方法,因為無法預知具體需要觸發本地OC方法時機,
b. 后者 js 和 OC約定同一個方法名做識別關鍵字iosHideBottomBar 年 在JSContext獲取webView js上下文,告訴js 本地有這個同樣方法名的OC方法,你可以隨時調用(子線程中).
體驗下來,發現 后者這種 webView 控制調用更合理,在需要的時候調用. 但是現在好多第三方 用的都是前者,攔截url判斷的方法,比如"有贊"就是,所以兩種方法 我們都應該掌握,在需要的的時候選擇相對更優的方式處理實現
native OC 調用 js
調用時機是應該是 webFinishLoad后,
(1)stringByEvaluatingJavaScriptFromString : 我常用這個方法去獲取 webView標題
NSString *title = [webView stringByEvaluatingJavaScriptFromString:@"document.title"];
@"document.title" 傳遞給js (js call native) 是異步的
當js 返回的 title字符串 (native call js) 是同步得到的
stringByEvaluatingJavaScriptFromString 方法應該在主線程中執行,
這個方法也可以oc js相互傳遞參數
這個方法調用時機:應該在webview 請求完成后再調用 js 方法,這里才能用stringByEvaluatingJavaScriptFromString,因為
要等頁面加載完,頁面沒加載完就相當於有些對象不一定創建成功,那么用js時候就容易找不到對象
(2)
//創建對象 context 獲取 js上下文 JSContext *context=[webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"]; \
NSString *jsString=@"iosHideBottomBar('參數1','參數2')"; //准備執行的js代碼 並向iOS 傳遞參數1 參數2 [context evaluateScript:jsString];//通過oc方法調用js的 iosHideBottomBar
其他情況:
我還特意考慮了 相互需要返回值的可能
在具體上面方法舉例,使用方法名時候 有特意強調參數,和返回值的情況不再贅述
參考:
http://blog.devtang.com/2012/03/24/talk-about-uiwebview-and-phonegap/
http://www.jianshu.com/p/d19689e0ed83