iOS開(kāi)發(fā)---OC和JS交互一:UIWebView和OC的交互

iOS發(fā)展至今, 各種hybird方案層出不窮. 最經(jīng)典的hybird方案莫過(guò)于在native框架中, 使用UIWebView來(lái)展示HTML頁(yè)面. 那么我們需要去考慮怎么與web前端進(jìn)行交互. 最近項(xiàng)目中有與web前端進(jìn)行交互, 遇到了一些坑點(diǎn), 故而在這里整理下UIWebView和iOS進(jìn)行交互的方案.

一、協(xié)議攔截

(1)、JS調(diào)用OC:
  • 邏輯: 點(diǎn)擊JS中的button, 傳遞一個(gè)參數(shù)給OC, OC將傳遞來(lái)的參數(shù)打印出來(lái)

  • 實(shí)現(xiàn):


    JS調(diào)用OC
  • JS中代碼:

<div style="height: 200px">
        <button onclick = "clickAction0(1111111)" style = "font-size: 40px; width: 400px; height: 100px" >
            button
        </button>
    </div>
function clickAction0(clickId) {
            var token = "js_tokenString";
            loginSucceed(token);
        }
function loginSucceed(token) {
            var action = "loginSucceed";
            jsToOc(action, token);
        }
function jsToOc(action, params) {
            var url = "jsToOc://" + action + "?" + params;
            loadURL(url);
        }
function loadURL(url) {
            window.location.href = url;
        }
  • OC代碼:
//!< uwebView每次加載請(qǐng)求前都會(huì)調(diào)用這個(gè)方法
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
    
    //!< 通過(guò)判斷URL.scheme來(lái)判斷是否是JS中定義好的方法
    if ([request.URL.scheme caseInsensitiveCompare:@"jsToOc"] == NSOrderedSame) {
        [self presentAlertWithTitle:request.URL.host message:request.URL.query handler:nil];
        return NO;
    }
    return YES;
}
  • 原理:
    • iOS與JS定義好jsToOC協(xié)議, 用作JS調(diào)用OC時(shí)的url.scheme;
    • JS在按鈕點(diǎn)擊后加載URL:jsToOc://loginSucceed?js_tokenString;
    • UIWebView會(huì)在每次loadRequest時(shí), 調(diào)用回調(diào):- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;
    • 判斷request的url.scheme是否是定義好的協(xié)議
(2)、OC調(diào)用JS
  • 邏輯: 點(diǎn)擊native的登錄按鈕, 將登錄成功后的數(shù)據(jù)回傳給JS

  • 實(shí)現(xiàn):
    OC調(diào)用JS代碼, 并傳遞參數(shù)
  • JS代碼:

<div id = "returnValue" style = "font-size: 40px; border: 1px dotted; height: 100px;">
        
    </div>
function OCTransferJS(action, params) {
<!--            alert('JS代碼被OC調(diào)用了' + action + params);-->
            document.getElementById("returnValue").innerHTML = 'JS代碼被OC調(diào)用, 并向JS傳遞了參數(shù)' + action + '?' + params;
        }
  • OC代碼:
#pragma mark - OC調(diào)用JS  OC調(diào)用JS代碼, 并傳遞參數(shù)
//!< 點(diǎn)擊native按鈕登錄, 并將登錄成功的參數(shù)回傳給JS
- (void)login:(UIButton *)sender {
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        
        NSString *jsString = [NSString stringWithFormat:@"OCTransferJS('loginSucceed', 'oc_tokenString')"];
        [self.webView stringByEvaluatingJavaScriptFromString:jsString];
//         JSContext *context = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
//        [context evaluateScript:[NSString stringWithFormat:@"OCTransferJS('login_success', 'login_success_token')"]];
    });
}
  • 原理:
    • iOS與JS約定好OCTransferJS方法, 用作入口方法
    • OC 使用 - stringByEvaluatingJavaScriptFromString方法訪問(wèn)JS
    • OC在登錄成功后將參數(shù)回傳給JS

- stringByEvaluatingJavaScriptFromString 只有在整個(gè)webViwe加載完畢后才會(huì)執(zhí)行

二、使用JavaScriptCore框架

通過(guò)協(xié)議攔截的方式, 可以進(jìn)行OC與JS之間的交互, 但是太過(guò)繁瑣了.
Apple在iOS7中提供了一個(gè)功能強(qiáng)大的框架:JavaScriptCore, 簡(jiǎn)化了UIWebView與OC的交互

(1)、JS調(diào)用OC
  • 邏輯: 點(diǎn)擊HTML中的登錄按鈕, 傳遞登錄成功的參數(shù)給OC, OC展示登錄成功的參數(shù)

  • 實(shí)現(xiàn):
    JS登錄成功將參數(shù)傳遞給OC.gif
  • JS代碼:

<button onclick = "login()" style = "font-size: 40px;">登錄</button>
function login() {
            var token = "js_tokenString";
            loginSucceed(token);
        }
//!< 登錄成功
        function loginSucceed(token) {
            var action = "loginSucceed";
            JSTransferOC(action, token);
        }
    
        //!< JS調(diào)用OC入口
        function JSTransferOC(action, params) {
            var url = "JSTransferOC://" + action + "?" + params;
            loadURL(url);
        }
    
        //!< 加載URL
        function loadURL(url) {
            window.location.href = url;
        }
  • OC代碼:
#import <JavaScriptCore/JavaScriptCore.h>
- (void)webViewDidFinishLoad:(UIWebView *)webView {
    
    JSContext *context = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
    
    context[@"JSTransferOC"] = ^(NSString *action, NSString *params) {
        [self presentAlertWithTitle:[NSString stringWithFormat:@"獲取到點(diǎn)擊js按鈕的事件, 傳遞過(guò)來(lái)了事件:%@" , action] message:params handler:nil];
    };
}
  • 原理:
    • JS和OC定義好一個(gè)入口函數(shù)JSTransferOC
    • OC通過(guò)JSContext *context = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"]; 獲取JSContext上下文
    • JS調(diào)用JSTransferOC函數(shù), OC以Block形式監(jiān)聽(tīng)此方法, context[@"JSTransferOC"] = ^() {}, 收到JS的參數(shù)回調(diào)

Tips: 以Block形式監(jiān)聽(tīng)函數(shù)時(shí), 不要在Block內(nèi)部使用JSValue或者JSContext, 因?yàn)?code>JSContext強(qiáng)引用Block, Block強(qiáng)引用外部變量. JSValue需要JSContext來(lái)執(zhí)行JS代碼, 故而JSVaule又強(qiáng)引用JSContext, 會(huì)形成循環(huán)引用. 因?yàn)镴S中沒(méi)有弱引用的概念, 所以__weak沒(méi)有作用. 可以用將JSValue作為Block內(nèi)部變量的方式 和 [JSContext currentContext]的方式來(lái)解決兩種循環(huán)引用

(2)、OC調(diào)用JS
  • 邏輯:點(diǎn)擊原生的登錄按鈕, 將登錄成功的數(shù)據(jù)傳遞給JS, JS進(jìn)行展示

  • 實(shí)現(xiàn):
    OC調(diào)用JS
  • JS代碼:

<div id = "returnValue" style = "font-size: 40px; border: 1px dotted; height: 100px;">
    </div>
function OCTransferJS(action, params) {
            document.getElementById("returnValue").innerHTML = 'JS代碼被OC調(diào)用, 并向JS傳遞了參數(shù)' + action + '?' + params;
        }
  • OC代碼:
#pragma mark - OC調(diào)用JS  OC調(diào)用JS代碼, 并傳遞參數(shù)
//!< 點(diǎn)擊native按鈕登錄, 并將登錄成功的參數(shù)回傳給JS
- (void)login:(UIButton *)sender {
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        //!< 方式二: 使用JavaScriptCore
         JSContext *context = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
        [context evaluateScript:[NSString stringWithFormat:@"OCTransferJS('login_success', 'login_success_token')"]];
    });
}
  • 原理:
    • JS和iOS預(yù)定好入口函數(shù):OCTransferJS
    • OC通過(guò)JSContext *context = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];方式來(lái)獲取JS上下文JSContext;
    • JSContext通過(guò)evaluateScript函數(shù)執(zhí)行JS代碼OCTransferJS('login_success', 'login_success_token');

PS: 筆者在項(xiàng)目中遇到過(guò)傳遞對(duì)象的情況,將NSDictionary轉(zhuǎn)換成JSON字符串包裝在參數(shù)中, 類似這種[NSString stringWithFormat:@"OCTransferJS('%@')", resustJsonStr], 不過(guò)不能成功傳遞. 推測(cè)可能是這樣調(diào)用的話, JS不能識(shí)別, 還望有知道答案的大牛不吝賜教~

三、JSExport協(xié)議

JSExportJavaScriptCore框架中的一個(gè)協(xié)議. 如果一個(gè)對(duì)象遵循了JSExport協(xié)議, 那么該對(duì)象的方法會(huì)對(duì)JS開(kāi)放, 允許JS直接調(diào)用;

(1)、JS調(diào)用OC
  • 邏輯: JS中點(diǎn)擊按鈕, 調(diào)用OC對(duì)象中的方法, 并傳遞參數(shù).

  • 實(shí)現(xiàn):
    JS調(diào)用OC本地代碼,并傳遞參數(shù)
  • JS代碼:

<div style="height: 200px; margin-top: 20px">
        <button onclick = "clickbtn()" style = "font-size: 40px; width: 400px; height: 100px" >
            js調(diào)用OC代碼
        </button>
    </div>
function clickbtn() {
            var str = window.iOSDelgate.onClick("點(diǎn)擊了button", "調(diào)用成功");
            alert(str);
        }
  • OC代碼:
//!< 定義protocol

@protocol JSObjectDelegate <JSExport>

//!< 取一個(gè)JS能夠識(shí)別的名字
JSExportAs(onClick, - (NSString *)onClick:(NSString *)param1 param2:(NSString *)param2);

@end
//!< 遵循代理 讓JS能夠調(diào)用到方法
@interface UIWebViewVC : UIViewController<JSObjectDelegate, UIWebViewDelegate>

@end
- (void)webViewDidFinishLoad:(UIWebView *)webView {
    
    JSContext *context = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
    
    //!< 將JS和OC中定義好的類(iOSDelgate)指向自身, 這樣JS中的 window.iOSDelgate.onClick();就會(huì)調(diào)用oc代碼
    context[@"iOSDelgate"] = self;
}
#pragma mark - 實(shí)現(xiàn)protocol方法, 供JS調(diào)用, JS調(diào)用OC本地代碼, 常用在定位, 獲取相冊(cè)等操作
//!< 點(diǎn)擊HTML中的按鈕, 調(diào)用OC中的代碼, 并回傳參數(shù)給JS
- (NSString *)onClick:(NSString *)param1 param2:(NSString *)param2 {
    
    return [NSString stringWithFormat:@"OC方法被JS調(diào)用%@%@", param1, param2];
}
  • 原理:
    • JS和OC定義好入口函數(shù)onClickiOSDelgate類;
    • OC創(chuàng)建遵循JSExport協(xié)議的JSObjectDelegate協(xié)議, 并在協(xié)議中聲明- onClick: param2:方法;
    • JSExportAs() 將OC方法重命名, 以便JS能夠識(shí)別;
    • OC類遵循JSExport協(xié)議, 并實(shí)現(xiàn)協(xié)議方法- onClick: param2:;
    • JS調(diào)用入口函數(shù)onClick, 并傳遞參數(shù);
    • OC獲取參數(shù), 經(jīng)過(guò)處理, 再次回傳參數(shù)給JS, JS進(jìn)行展示;

Tips: 若OC方法沒(méi)有參數(shù), 則不需要JSExportAs()進(jìn)行重命名;

(2)、OC調(diào)用JS:

與上述方法一致.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容