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