iOS與JS交互總結

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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,327評論 6 537
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,996評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,316評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,406評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,128評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,524評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,576評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,759評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,310評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,065評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,249評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,821評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,479評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,909評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,140評論 1 290
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,984評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,228評論 2 375

推薦閱讀更多精彩內容

  • 轉至元數據結尾創建: 董瀟偉,最新修改于: 十二月 23, 2016 轉至元數據起始第一章:isa和Class一....
    40c0490e5268閱讀 1,753評論 0 9
  • 這篇文章完全是基于南峰子老師博客的轉載 這篇文章完全是基于南峰子老師博客的轉載 這篇文章完全是基于南峰子老師博客的...
    西木閱讀 30,579評論 33 466
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,690評論 25 708
  • 把瑜伽拉伸動作校正了下,每天若有其事、學啥就要學出個樣子,本著這顆心,開始吧 絕不拖延、這是最近潛移默化顯現出來的...
    Molly喵小北閱讀 226評論 0 3
  • 人生最大的修養是包容 人生最大的修養是包容。它既不是懦弱也不是忍讓,而是察人之難,補人之短,揚人之長,諒人之過,而...
    xcy無名閱讀 236評論 0 0