iOS與JS交互總結(jié)
近幾年來移動開發(fā)使用網(wǎng)頁嵌入形式的越來越多,這就不可避免的出現(xiàn)原生控件和網(wǎng)頁頁面的JS交互,本篇就大概總結(jié)一下目前iOS開發(fā)中原生控件與JS的交互的幾種形式。</br>
iOS開發(fā)中使用的是UIWebView控件來加載網(wǎng)頁頁面資源的。所以我們也就主要圍繞這個控件來總結(jié)一下,大體上可以分為三種形式:
1、原生api交互,直接執(zhí)行腳本,攔截代理
2、第三方庫WebViewJavascriptBrige交互,實質(zhì)還是攔截代理
3、JavaScriptCore框架
? 3.1 context上下文,context上下文直接設置和調(diào)用
? 3.2 JSExport協(xié)議,通過JSExport協(xié)議設置和調(diào)用
接下來就一個一個的來看看到底是如何實現(xiàn)的,大家也可以提前把demo下載下來,結(jié)合代碼來看效果會更好哦。
原生api交互
使用原生API來實現(xiàn)交互,實際上主要用到一個函數(shù)和一個協(xié)議,
函數(shù):
- (nullable NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script;
協(xié)議:
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;
1、 OBJC調(diào)用JS
直接使用UIWebView的函數(shù)執(zhí)行JS代碼,
比如我們JS代碼中有一個函數(shù)objcCallJS,
function objcCallJS() {
var data = 'ljt'
alert('來自objc的調(diào)用,有返回值:' + data);
return data
}
那我們該如何去調(diào)用呢?
直接在相應的地方如此調(diào)用即可:
NSString *returnStr = [self.homeWebView stringByEvaluatingJavaScriptFromString:@"objcCallJS()"];
NSLog(@"返回值為:%@",returnStr);
我們注意到這個地方會有個返回值,此時如果我們調(diào)用的JS代碼有返回值得話,就會賦值到returnStr這個變量,可供我們后續(xù)使用。如果JS代碼沒有返回值得話,這個值默認是Undefine。
OBJC調(diào)用JS代碼就是如此的簡單。這個時候,有人說了,如果我們想向JS代碼中傳值該怎么弄呢?
JS代碼:
function objcCallJSParam(param1,param2) {
alert('來自objc的調(diào)用,帶有參數(shù):' + param1 + ' ' + param2);
}
OBJC代碼:
- (void)callJSBtnParamAction:(id)sender {
NSLog(@"開始調(diào)用JS函數(shù),帶有參數(shù)");
[self.homeWebView stringByEvaluatingJavaScriptFromString:@"objcCallJSParam('ljt','ths')"];
}
這樣我們就可以向JS代碼中傳入?yún)?shù)了。
2、 JS調(diào)用OBJC
在這里我們使用攔截UIWebView的代碼來實現(xiàn),我們知道代理函數(shù)- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;會在UIwebView開始加載頁面的時候調(diào)用,如果我們返回NO則這個頁面什么也不做,如果返回YES則加載這個頁面。有了這個特性,我們就可以來實現(xiàn)JS調(diào)用OBJC的代碼。
</br>具體的流程: 頁面響應時,重新設置頁面的href,并將OBJC所用的參數(shù)以及所調(diào)用的指定函數(shù),封裝成一個特定格式的URL鏈接,然后我們在UIwebView的協(xié)議中攔截這個鏈接,然后解析出來,根據(jù)固定的格式做出相應的處理。
具體來說:當我們頁面中一個事件,設置了頁面的href,攜帶了兩個參數(shù),
//調(diào)用OBJC
function JSCallObjc() {
window.location.href="www.baidu.com/param=ljt&ths";
}
我們在OBJC中可以這么做:
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
NSLog(@"將要開始加載頁面");
NSString *urlStr = [[request URL] absoluteString];
if ([urlStr containsString:@"param"]) {
NSRange range = [urlStr rangeOfString:@"param="];
NSString *paramStr = [urlStr substringFromIndex:range.location + range.length];
NSLog(@"param=%@",paramStr);
UIAlertController *alterVC = [UIAlertController alertControllerWithTitle:@"objc彈框" message:paramStr preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil];
[alterVC addAction:okAction];
[self presentViewController:alterVC animated:YES completion:nil];
return NO;
}
return YES;
}
這樣就實現(xiàn)了JS調(diào)用OBJC的效果,當然這個例子不是那么好,href的格式我是隨便寫寫,當真正使用起來的時候需要事先設計好合理的封裝方式。
</br></br>
第三方庫WebViewJavascriptBrige交互
這個部分我們使用有名的第三方庫WebViewJavascriptBrige來介紹一下,關于這個庫的實現(xiàn),網(wǎng)上已經(jīng)有很多介紹,關于原理的實現(xiàn)就不多介紹了,直接進入使用環(huán)節(jié),使用這個庫的時候有一個地方需要注意一下,在所加載的頁面中需要實現(xiàn)固定的寫法:
//固定函數(shù),必須這樣寫
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 = 'wvjbscheme://__BRIDGE_LOADED__';
document.documentElement.appendChild(WVJBIframe);
setTimeout(function() { document.documentElement.removeChild(WVJBIframe) }, 0)
}
//所有交互的函數(shù)都寫在這里面
setupWebViewJavascriptBridge(function(bridge) {
····
})
1、 OBJC調(diào)用JS
首先我們需要在JS中注冊一個函數(shù),供OBJC來調(diào)用
bridge.registerHandler('objcCallJS', function(data, responseCallback) {
myAlert(data)
var responseData = { 'Javascript Says':'Right back atcha!' }
responseCallback(responseData)
})
這樣注冊之后,我們就可以在OBJC代碼中調(diào)用這個名為objcCallJS的函數(shù)了。
OBJC中的代碼:
- (void)callJSBtnAction:(id)sender {
NSLog(@"調(diào)用JS函數(shù)");
//callHandler有幾種形式
//- (void)callHandler:(NSString *)handlerName 只調(diào)用函數(shù)
//- (void)callHandler:(NSString *)handlerName data:(id)data 調(diào)用的同時攜帶數(shù)據(jù)
//- (void)callHandler:(NSString *)handlerName data:(id)data responseCallback:(WVJBResponseCallback)responseCallback 不但調(diào)用和攜帶數(shù)據(jù),而且設置回調(diào)函數(shù)處理所需的數(shù)據(jù)(如果需要處理結(jié)果數(shù)據(jù))
// [self.bridge callHandler:@"objcCallJS" data:@{@"key":@"value"} responseCallback:^(id responseData){
// NSLog(@"%@",responseData);
// }];
// [self.bridge callHandler:@"objcCallJS"];
[self.bridge callHandler:@"objcCallJS" data:@{@"key":@"value"}];
}
這個地方有幾處我們需要說明的地方
1、objcCallJS這個名稱必須是一樣的
2、OBJC中調(diào)用JS的函數(shù)是通過- (void)callHandler:(NSString*)handlerName data:(id)data;這個借口調(diào)用的,這個接口的最終調(diào)用實現(xiàn)是- (void)callHandler:(NSString*)handlerName data:(id)data responseCallback:(WVJBResponseCallback)responseCallback;,當我們調(diào)用的時候,<strong>handlerName</strong>對應JS代碼中注冊的函數(shù)名,<strong>data</strong>對應JS代碼中的data,即為OBJC向JS中傳入的參數(shù),還有一個有意思的參數(shù)就是<strong>responseCallback</strong>,就是JS代碼的responseCallback,也就是說,當我們的JS代碼處理完成之后,OBJC還有一次機會可以對JS代碼處理完成的邏輯進行處理。當然data和responseCallback這兩個參數(shù)都不是必須的。
2、 JS調(diào)用OBJC
與OBJC調(diào)用JS的邏輯類似,
首先我們需要在OBJC中注冊JS能夠調(diào)用的函數(shù):
//注冊js調(diào)用函數(shù),并設定回調(diào)。js中可以調(diào)用JSCallObjc的函數(shù)
[self.bridge registerHandler:@"JSCallObjc" handler:^(id data, WVJBResponseCallback responseCallback){
NSString *paramStr = [data objectForKey:@"key"];
UIAlertController *alterVC = [UIAlertController alertControllerWithTitle:@"objc彈框" message:paramStr preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil];
[alterVC addAction:okAction];
[self presentViewController:alterVC animated:YES completion:nil];
responseCallback(@"Response from testObjcCallback");
}];
注冊完成以后,我們就可以在JS代碼中調(diào)用這個注冊好的函數(shù)了:
//調(diào)用OBJC中的函數(shù)
var callbackButton = document.getElementById('btn')
callbackButton.onclick = function(e) {
e.preventDefault()
//callHandler的參數(shù)可變
//bridge.callHandler('JSCallObjc') 沒有攜帶數(shù)據(jù)
//bridge.callHandler('JSCallObjc', {'key': 'value'}) 攜帶數(shù)據(jù)
//bridge.callHandler('JSCallObjc', {'key': 'value'},function(response) {
// log('JS got response', response)
// }) 攜帶參數(shù)和回調(diào)函數(shù)
//bridge.callHandler('JSCallObjc')
bridge.callHandler('JSCallObjc',{'key': 'value'})
}
我們可以看到,這個邏輯和接口與OBJC調(diào)用JS的邏輯和接口大同小異,具體的參數(shù)也是差不多的。
到此我們就可以利用這個第三方庫來實現(xiàn)OBJC和JS的互相調(diào)用。其實這個庫的內(nèi)部還是通過攔截協(xié)議來實現(xiàn)這個交互過程的。
JavaScriptCore框架
JavaScriptCore框架是在iOS7之后引入的框架,這個框架在JS交互上為我們提供了很大幫助,可以在html界面上調(diào)用OC方法并傳參,也可以在OC上調(diào)用JS方法并傳參。關于JavaScriptCore的詳細介紹和注意事項,請大家自行google之。這里我們只是簡單介紹一下用法,很淺顯,請大家不要見怪。
使用之前,我們需要導入JavaScriptCore.framework這個框架
context上下文,context上下文直接設置和調(diào)用
1、 OBJC調(diào)用JS
OBJC調(diào)用JS之前,需要獲取一下JS的上下文,
self.context = [self.homeWebView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
NSLog(@"self.contex=%@",self.context);
獲取到這個上下文之后,我們就可以對JS的執(zhí)行環(huán)境做處理了,假如我們需要調(diào)用JS中已經(jīng)實現(xiàn)過的函數(shù),我們可以直接調(diào)用相應的函數(shù)就行:
- (void)callJSBtnAction:(id)sender {
NSLog(@"開始調(diào)用JS函數(shù),有返回值");
//第一種
// NSString *returnStr = [self.homeWebView stringByEvaluatingJavaScriptFromString:@"objcCallJS()"];
// NSLog(@"返回值為:%@",returnStr);
//第二種
// NSString *js = @"objcCallJS()";
// JSValue *value = [self.context evaluateScript:js];
// NSLog(@"%@",[value toString]);
//第三種
JSValue *valueFuc = self.context[@"objcCallJS"];
JSValue *value = [valueFuc callWithArguments:nil];
NSLog(@"%@",[value toString]);
}
以上代碼中我們展示了三種不同的調(diào)用方式,特別說明的是第三種,首先我們從上下文中獲取要執(zhí)行的函數(shù),并把它保存在一個JSValue變量中JSValue *valueFuc = self.context[@"objcCallJS"];,然后對這個變量調(diào)用其函數(shù)- (JSValue *)callWithArguments:(NSArray *)arguments;傳入?yún)?shù)。
我們可以在JS中這樣使用:
因為我們傳入的參數(shù)為nil,所以我們在JS中沒有獲取參數(shù),代碼demo中我們提供了獲取參數(shù)的版本,具體的可以參照代碼demo,
function objcCallJS() {
var data = 'ljt'
alert('來自objc的調(diào)用,有返回值:' + data);
return data
}
2、 JS調(diào)用OBJC
首先我們在OBJC中,增加JS可以執(zhí)行的代碼,怎么添加呢?還是需要用到上面所提到的上下文context。
加入我們需要實現(xiàn)一個可以接收參數(shù)的函數(shù)名為JSCallObjcParam,該怎么寫呢?
//有參數(shù)
__weak typeof(self) weakSelf = self;
self.context[@"JSCallObjcParam"] = (id)^(NSString *param1, NSString *param2) {
NSLog(@"有參,無返回值");
NSString *message = [NSString stringWithFormat:@"%@:%@",param1,param2];
UIAlertController *alterVC = [UIAlertController alertControllerWithTitle:@"objc彈框" message:message preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil];
[alterVC addAction:okAction];
[weakSelf presentViewController:alterVC animated:YES completion:nil];
};
這樣我們就實現(xiàn)了一個可以在JS中調(diào)用的函數(shù)JSCallObjcParam,這個函數(shù)就收兩個參數(shù)。
我們在JS中這樣調(diào)用:
//調(diào)用OBJC
function callObjcParam(param1,param2) {
var data = JSCallObjcParam('ljt','ths')
alert('來自objc(callObjcParam)的返回值:' + data);
}
這么兩步我們就實現(xiàn)了iOS和JS的相互調(diào)用。</br>
demo中我們分別實現(xiàn)了相互調(diào)用的傳參和不傳參,有返回值和沒有返回值的情況,具體的可以看demo代碼。
JSExport協(xié)議,通過JSExport協(xié)議設置和調(diào)用
JSExport是一個協(xié)議,我們可以自定義一個協(xié)議,繼承此協(xié)議,在協(xié)議中聲明可以在JS中使用的API函數(shù)。
首先我們實現(xiàn)一個這么樣的協(xié)議ObjcJSDelegate
@protocol ObjcJSDelegate <JSExport>
//有參數(shù)
- (void)JSCallObjcParam:(NSString *)param1 with:(NSString *)param2;
//無參數(shù)
- (void)JSCallObjc;
//有返回值
- (NSString *)JSCallObjcReturn;
@end
<strong>在這里我們需要知道的是,這個協(xié)議中所聲明的接口是供JS調(diào)用的,也就是說JS代碼調(diào)用OBJC的相關接口,可以放在這個協(xié)議中。而OBJC調(diào)用JS還是通過上下文來直接調(diào)用的</strong>
其次,我們需要一個實現(xiàn)了這個代理的類,我們就直接實現(xiàn)在@interface JSExportViewController ()<ObjcJSDelegate>UIWebView所在的ViewController中:
#pragma mark ObjcJSDelegate
- (void)JSCallObjcParam:(NSString *)param1 with:(NSString *)param2 {
NSLog(@"有參,無返回值");
NSString *message = [NSString stringWithFormat:@"%@:%@",param1,param2];
UIAlertController *alterVC = [UIAlertController alertControllerWithTitle:@"objc彈框" message:message preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil];
[alterVC addAction:okAction];
[self presentViewController:alterVC animated:YES completion:nil];
}
- (void)JSCallObjc {
NSLog(@"無參,無返回值");
}
- (NSString *)JSCallObjcReturn {
NSLog(@"無參,有返回值");
return @"ljt";
}
然后我們需要讓JS知道如何調(diào)用我們所需要的函數(shù),這個時候同樣需要前面一直提到的上下文:
self.context[@"OBJCFuc"] = self;
如此,我們就可以在JS中通過OBJCFuc(OBJCFuc.接口名字)來調(diào)用我們之前協(xié)議實現(xiàn)的相關接口了:
JS調(diào)用
//調(diào)用OBJC
function callObjcParam(param1,param2) {
var data = OBJCFuc.JSCallObjcParamWith('ljt','ths')
alert('來自objc(callObjcParam)的返回值:' + data);
}
function callObjc() {
var data = OBJCFuc.JSCallObjc()
alert('來自objc(callObjc)的返回值:' + data);
}
function callObjcReturn() {
var data = OBJCFuc.JSCallObjcReturn()
alert('來自objc(callObjcReturn)的返回值:' + data);
}
這樣就實現(xiàn)了JS調(diào)用OJBC。
這篇文章,只是簡單的介紹了iOS和JS的交互的幾種方法,很粗淺,很簡單,但也許很實用,最后附上代碼demo