Demos
iOS與JS交互的幾種方式
- JavaScriptCore:iOS7之后出現(xiàn)的,學(xué)習(xí)成本不高,是適配iOS7的首選。
- 攔截協(xié)議:攔截協(xié)議需要雙方共同協(xié)商為協(xié)議規(guī)定一套準(zhǔn)則,在交互中要遵循該準(zhǔn)則。攔截協(xié)議不需要引入任何框架,適合多個(gè)平臺使用。協(xié)議可以如此定義:
schemes://model/action?{參數(shù)1}={數(shù)值1}&{參數(shù)2}={數(shù)值2}&...。 - 第三方框架WebViewJavaScriptBridge:基于攔截協(xié)議進(jìn)行的封裝,學(xué)習(xí)成本相對JavaScriptCore較高,使用不如JavaScriptCore方便。
- WKWebView:iOS8之后出現(xiàn)的。
iOS7之前,Objective-C調(diào)用JavaScript代碼
iOS7以前,Objective-C調(diào)用JavaScript的方式只有一種,就是通過UIWebView對象的stringByEvaluatingJavaScriptFromString:方法。
-
stringByEvaluatingJavaScriptFromString:方法只能在主線程執(zhí)行
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_async(queue, ^{
[self.webView stringByEvaluatingJavaScriptFromString:@"var javascript = 1 + 2"];
});
- 通過
stringByEvaluatingJavaScriptFromString:方法可以簡單地調(diào)用系統(tǒng)提供的JavaScript方法
- (void)webViewDidFinishLoad:(UIWebView *)webView{
NSString *title = [webView stringByEvaluatingJavaScriptFromString:@"document.title"];
NSLog(@"%@", title);
}
iOS7之前,JavaScript調(diào)用Objective-C
- URL請求攔截。
在Objective-C代碼里設(shè)置UIWebViewDelegate代理,實(shí)現(xiàn)代理方法
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;
解釋:該方法可以監(jiān)聽到UIWebView中發(fā)出的URL請求,通過與H5協(xié)商一個(gè)URL通信協(xié)議,來攔截指定的URL,做相應(yīng)的操作,并阻止此鏈接的跳轉(zhuǎn)。
例子:
html代碼
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div style="margin-top: 10px">
<input type="button" value="callPhone" onclick="callPhone()">
</div>
</body>
<script>
// 聲明一個(gè)名為callPhone的js函數(shù),其會發(fā)出一個(gè)鏈接為nativejs://callPhone的請求
function callPhone() {
window.location.href = 'nativejs://callPhone';
}
</script>
</html>
objc代碼
/**
* 在一個(gè)網(wǎng)頁開始加載一個(gè)frame前被調(diào)用
*/
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType{
NSLog(@"shouldStartLoadWithRequest-------");
NSString *urlString = request.URL.absoluteString;
NSRange range = [urlString rangeOfString:@"nativejs://"];
if (range.location != NSNotFound) { // 攔截URL協(xié)議頭是nativejs的鏈接
NSLog(@"執(zhí)行原生調(diào)用相機(jī)的方法");
return NO;// 阻止此鏈接的跳轉(zhuǎn)
}
return YES;
}
- 監(jiān)聽Cookie。
詳見參考鏈接的原生與H5的交互一文。
iOS7之后,JavaScriptCore的引入,使得Objective-C與JavaScript的交互更為容易
JavaScriptCore中常見的幾種類型
- JSContext:代表JS的執(zhí)行環(huán)境,通過
evaluateScript:方法就可以執(zhí)行JS方法。 - JSValue:封裝了JS與ObjC中對應(yīng)的模型,以及調(diào)用JS的API等。
- JSExport:一個(gè)協(xié)議,通過遵守此協(xié)議,可以定義我們自己的協(xié)議,在協(xié)議中聲明的API都會在JS中暴露出來,能被JS調(diào)用。
- JSManagedValue:管理數(shù)據(jù)和方法的類。
- JSVirtualMachine:處理線程相關(guān),使用較少。
Objective-C調(diào)用JavaScript
/**
* 網(wǎng)頁加載完畢時(shí)被調(diào)用
*/
- (void)webViewDidFinishLoad:(UIWebView *)webView{
// 獲取當(dāng)前JS執(zhí)行環(huán)境
JSContext *context = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
NSString *alertJS = @"alert('Hello JS!')"; //準(zhǔn)備執(zhí)行的JS代碼
// 通過evaluateScript:方法調(diào)用JS的alert
[context evaluateScript:alertJS];
}
JavaScript 調(diào)用 Objective-C
-
直接調(diào)用JS(不適用于實(shí)戰(zhàn)項(xiàng)目中)。
例子1:
objc代碼// 獲取當(dāng)前JS執(zhí)行環(huán)境 JSContext *context = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"]; // context直接執(zhí)行JS代碼。 [context evaluateScript:@"var num = 10"]; [context evaluateScript:@"var squareFunc = function(value) { return value * value }"]; // 計(jì)算正方形的面積 JSValue *squareArea = [context evaluateScript:@"squareFunc(num)"]; NSLog(@"squareArea:%@", squareArea.toNumber); // 也可以通過下標(biāo)的方式獲取到JS函數(shù) JSValue *squareFunc = context[@"squareFunc"]; JSValue *squareArea2 = [squareFunc callWithArguments:@[@"20"]]; NSLog(@"squareArea2:%@", squareArea2.toNumber);例子2:
html代碼<div style="margin-top: 10px"> <input type="button" value="log" onclick="log('測試')"> </div>objc代碼
/**
- 網(wǎng)頁加載完畢時(shí)被調(diào)用
*/
-
(void)webViewDidFinishLoad:(UIWebView *)webView{
// 獲取當(dāng)前JS執(zhí)行環(huán)境
JSContext *context = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
// 直接調(diào)用JS代碼
context[@"log"] = ^(){
// 取出JS方法的參數(shù)
NSArray *args = [JSContext currentArguments];
for (id obj in args) {
NSLog(@"%@",obj); // 打印JS方法接收到的所有參數(shù)
}
};
}
- 在ObjC中通過JSContext注入模型,然后調(diào)用模型的方法。(重要,項(xiàng)目一般用該方式)
第一步:需要聲明一個(gè)與JS進(jìn)行交互的協(xié)議(NativeApisProtocol),要求該協(xié)議遵守JSExport協(xié)議。
第二步:新建一個(gè)模型(NativeAPIs),要求該模型遵守NativeApisProtocol協(xié)議。一般而言,需要在NativeAPIs模型中聲明一個(gè)JSContext屬性,便于與JS交互。
第三步:實(shí)現(xiàn)該模型(NativeAPIs),即在NativeAPIs.m文件中實(shí)現(xiàn)NativeApisProtocol協(xié)議中定義的方法。
第四步:在ViewController.m的-webViewDidFinishLoad:方法中獲取當(dāng)前JS執(zhí)行環(huán)境(self.jsContext),然后將NativeAPIs模型注入到JS執(zhí)行環(huán)境。
注意:NativeApisProtocol協(xié)議中定義的方法是在子線程中執(zhí)行的,如果在所定義的方法中需要修改界面或者跳轉(zhuǎn)之類的,需要通過GCD回主線程操作。
缺陷:通過參考鏈接的JavaScript和Objective-C交互的那些事(續(xù))可知,在-webViewDidFinishLoad:方法中注入NativeAPIs模型存在一定的問題,因?yàn)檫@時(shí)候網(wǎng)頁還沒加載完,JavaScript若開始調(diào)用Objective-C代碼(即NativeApisProtocol協(xié)議中定義的方法),會出現(xiàn)調(diào)用不到方法的問題。
解決方法:在每次創(chuàng)建JSContext環(huán)境的時(shí)候,都注入NativeAPIs模型到JSContext環(huán)境中。更加具體的方法可以參考第三方庫UIWebView-TS_JavaScriptContext。通過引入UIWebView+TS_JavaScriptContext,讓ViewController遵守TSWebViewDelegate協(xié)議,實(shí)現(xiàn)代理協(xié)議中的方法-webView:didCreateJavaScriptContext:,以此獲取JSContext環(huán)境。
objc代碼
/*-- NativeAPIs.h ---*/
#import <Foundation/Foundation.h>
#import <JavaScriptCore/JavaScriptCore.h>
#import <UIKit/UIKit.h>
// 聲明與JS交互的協(xié)議
@protocol NativeApisProtocol <JSExport> // 遵守JSExport協(xié)議
// 調(diào)用系統(tǒng)相機(jī)
- (void)callCamera;
// 調(diào)用系統(tǒng)分享
- (void)share:(NSString *)shareInfo;
// 打開寧波手機(jī)閱讀
- (void)openNBPhoneReader;
@end
@interface NativeAPIs : NSObject <NativeApisProtocol>
@property(weak, nonatomic) JSContext *jsContext;
@end
/*--------------------------------------------*/
/*-- NativeAPIs.m --*/
/*-- 省略,具體看Demo源碼 --*/
/*--------------------------------------------*/
// ViewController.m
/*-- 省略前面的代碼 --*/
#pragma mark - UIWebViewDelegate
- (void)webViewDidFinishLoad:(UIWebView *)webView{
NSLog(@"網(wǎng)頁加載完畢------");
// 獲取JS上下文運(yùn)行環(huán)境
self.jsContext = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
NativeAPIs *nativeAPIs = [[NativeAPIs alloc] init];
// 將NativeAPIs模型注入JS
self.jsContext[@"NativeApis"] = nativeAPIs;
nativeAPIs.jsContext = self.jsContext;
self.jsContext.exceptionHandler = ^(JSContext *context, JSValue *exceptionValue) {
context.exception = exceptionValue;
NSLog(@"異常信息: %@", exceptionValue);
};
}
html代碼
<html>
/*-- 省略無關(guān)緊要的代碼,具體見Demo --*/
<body>
<div style="margin-top: 10px">
<input type="button" value="CallCamera" onclick="NativeApis.callCamera()">
</div>
<div style="margin-top: 50px">
<input type="button" value="Share" onclick="callShare()">
</div>
<div style="margin-top: 50px">
<input type="button" value="OpenReader" onclick="NativeApis.openNBPhoneReader()">
</div>
</body>
<script>
// 聲明一個(gè)名為picCallback的函數(shù),其參數(shù)為photo
var picCallback = function (photo) {
alert(photo);
}
// 聲明一個(gè)名為callShare的函數(shù)
var callShare = function () {
var shareInfo = JSON.stringify(
{ "title": "objc&js的交互",
"desc": "就是那些事"}
);
// 調(diào)用原生的share方法
NativeApis.share(shareInfo);
}
// 聲明一個(gè)名為shareCallback的函數(shù)
var shareCallback = function () {
alert('success');
}
</script>
</html>
參考鏈接
UIWebView 與 JS 交互(1):Objective-C 調(diào)用 Javascript
JavaScript和Objective-C交互的那些事(續(xù))
JavaScriptCore在實(shí)際項(xiàng)目中的使用的坑
UIWebView中Objective-C與JavaScript交互
UIWebView-TS_JavaScriptContext
UIWebview 的javascript與ios objective-c互動(dòng)傳參數(shù)
UIWebview 的javascript與ios objective-c互動(dòng)傳參數(shù)(Ⅱ)