iOS下JS與OC互相呼叫(六)--WKWebView + WebViewJavascriptBridge

weixin_34148340發表於2018-02-27

上一篇文章介紹了UIWebView 如何通過WebViewJavascriptBridge 來實現JS 與OC 的互相呼叫,這一篇來介紹一下WKWebView 又是如何通過WebViewJavascriptBridge 來實現JS 與OC 的互相呼叫的。WKWebView 下使用WebViewJavascriptBridge與UIWebView 大同小異。主要是示例化的類不一樣,一些與webView 相關的API呼叫不一樣罷了。

WKWebView 下使用WebViewJavascriptBridge來實現JS 與OC 的互相呼叫,也是通過攔截URL來實現的。 下面開始介紹WKWebView 如何通過WebViewJavascriptBridge 來實現JS 與OC 的互相呼叫。 關於下載WebViewJavascriptBridge,然後匯入工程的部分就不再贅述了。

第一步,建立WKWebView。

這一步,唯一需要注意的地方,就是不用再設定WKWebViewnavigationDelegate,下一步你就知道為什麼了。

- (void)initWKWebView
{
    WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
    configuration.userContentController = [WKUserContentController new];
    
    WKPreferences *preferences = [WKPreferences new];
    preferences.javaScriptCanOpenWindowsAutomatically = YES;
    preferences.minimumFontSize = 30.0;
    configuration.preferences = preferences;
    
    self.webView = [[WKWebView alloc] initWithFrame:self.view.frame configuration:configuration];
    
    NSString *urlStr = [[NSBundle mainBundle] pathForResource:@"index.html" ofType:nil];
    NSString *localHtml = [NSString stringWithContentsOfFile:urlStr encoding:NSUTF8StringEncoding error:nil];
    NSURL *fileURL = [NSURL fileURLWithPath:urlStr];
    [self.webView loadHTMLString:localHtml baseURL:fileURL];
    
    self.webView.UIDelegate = self;
    [self.view addSubview:self.webView];
}
複製程式碼

第二步,建立WebViewJavascriptBridge例項。

這裡與上一篇文章有一些不同,WKWebView 使用的是WKWebViewJavascriptBridge,而UIWebView 使用的是WebViewJavascriptBridge

_webViewBridge = [WKWebViewJavascriptBridge bridgeForWebView:self.webView];
// 如果控制器裡需要監聽WKWebView 的`navigationDelegate`方法,就需要新增下面這行。
[_webViewBridge setWebViewDelegate:self];
複製程式碼

上一步說了不用再設定WKWebViewnavigationDelegate,那是因為在{-bridgeForWebView:}內已經將WKWebViewnavigationDelegate設定為WKWebViewJavascriptBridge的例項了。

+ (instancetype)bridgeForWebView:(WKWebView*)webView {
    WKWebViewJavascriptBridge* bridge = [[self alloc] init];
    [bridge _setupInstance:webView];
    [bridge reset];
    return bridge;
}

- (void) _setupInstance:(WKWebView*)webView {
    _webView = webView;
    _webView.navigationDelegate = self;
    _base = [[WebViewJavascriptBridgeBase alloc] init];
    _base.delegate = self;
}
複製程式碼

第三步,註冊 js 要呼叫的Native 功能

為了便於維護,我將所有js 要呼叫Native 功能放在了一個方法裡新增,然後每個功能再單獨處理。 示例程式碼如下:

#pragma mark - private method
- (void)registerNativeFunctions
{
    [self registScanFunction];
    
    [self registShareFunction];
    
    [self registLocationFunction];
    
    [self regitstBGColorFunction];
    
    [self registPayFunction];
    
    [self registShakeFunction];
}

// 註冊的獲取位置資訊的Native 功能
- (void)registLocationFunction
{
    [_webViewBridge registerHandler:@"locationClick" handler:^(id data, WVJBResponseCallback responseCallback) {
        // 獲取位置資訊
        
        NSString *location = @"廣東省深圳市南山區學府路XXXX號";
        // 將結果返回給js
        responseCallback(location);
    }];
}
複製程式碼

關於- (void)registerHandler:(NSString *)handlerName handler:(WVJBHandler)handler,我們可以這樣理解,後面的block 引數是js 要呼叫的Native 實現,前面的handlerName 是這個Native 實現的別名。然後js 裡呼叫handlerName 這個別名,WebViewJavascriptBridge最終會執行block 裡的Native 實現。

第四步,在HTML新增關鍵的js

HMTL 裡在呼叫Native 功能之前,要先新增一個js 方法,然後主動呼叫一次該方法。 要新增的方法是:

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)
}
複製程式碼

如果你看過iOS下JS與OC互相呼叫(一)--UIWebView 攔截URL,你就會發現這個方法與loadURL很像。 然後在js 中要主動呼叫一次上述的setupWebViewJavascriptBridge

setupWebViewJavascriptBridge(function(bridge) {

      // 這裡註冊Native 要呼叫的js 功能。
     bridge.registerHandler('testJSFunction', function(data, responseCallback) {
        alert('JS方法被呼叫:'+data);
        responseCallback('js執行過了');
     })
     // 如果要有其他Native 呼叫的js 功能,在這裡按照上面的格式新增。
})
複製程式碼

主動呼叫setupWebViewJavascriptBridge有兩個目的: 1、執行一次wvjbscheme://__BRIDGE_LOADED__請求。 2、註冊Native 要呼叫的js 功能。

執行wvjbscheme://__BRIDGE_LOADED__,然後在WKWebView 的navigationDelegate方法中攔截該URL ,然後往HMTL中注入js。以下原始碼都摘自WebViewJavascriptBridge

- (void)webView:(WKWebView *)webView
decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction
decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
    if (webView != _webView) { return; }
    NSURL *url = navigationAction.request.URL;
    __strong typeof(_webViewDelegate) strongDelegate = _webViewDelegate;

    if ([_base isCorrectProcotocolScheme:url]) {
        // 在這裡攔截wvjbscheme://__BRIDGE_LOADED__
        if ([_base isBridgeLoadedURL:url]) {
            // 這裡會注入js 
            [_base injectJavascriptFile];
        } else if ([_base isQueueMessageURL:url]) {
            [self WKFlushMessageQueue];
        } else {
            [_base logUnkownMessage:url];
        }
        decisionHandler(WKNavigationActionPolicyCancel);
    }
    
    if (strongDelegate && [strongDelegate respondsToSelector:@selector(webView:decidePolicyForNavigationAction:decisionHandler:)]) {
        [_webViewDelegate webView:webView decidePolicyForNavigationAction:navigationAction decisionHandler:decisionHandler];
    } else {
        decisionHandler(WKNavigationActionPolicyAllow);
    }
}

- (void)injectJavascriptFile {
    //讀取js 內容
    NSString *js = WebViewJavascriptBridge_js();
    // 執行Native 的API,實現將js 注入 到HMTL中。
    [self _evaluateJavascript:js];
    if (self.startupMessageQueue) {
        NSArray* queue = self.startupMessageQueue;
        self.startupMessageQueue = nil;
        for (id queuedMessage in queue) {
            [self _dispatchMessage:queuedMessage];
        }
    }
}
複製程式碼

WKWebView 執行js 的API 與 UIWebView 有些不同,WKWebView 用的是{-evaluateJavaScript: completionHandler:},這個API 不會立刻返回執行結果,js 的執行結果會在block 中返回。

第五步,在js 中呼叫 Native 功能。

講完過程,終於到了 js 呼叫Native 的用法了。其實非常的簡單,例如我想要利用Native 獲取定位資訊,那麼在HTML中新增一個按鈕,onclick事件是locationClick(),按照如下實現即可。

function locationClick() {
    WebViewJavascriptBridge.callHandler('locationClick',null,function(response) {
        alert(response);
        document.getElementById("returnValue").value = response;
    });
}
複製程式碼

Native 執行完程式碼,將獲取到的定位資訊,通過callHandler 的第三方引數,回撥返回到js 中。 response 可以是單個值,也可以是陣列、鍵值對等。 當然如果我們呼叫Native 的時候,沒有引數或者不需要Native 返回資訊到js 中。我們還可以這樣寫:

// 沒有引數,有回撥可以這樣寫
function locationClick() {
    WebViewJavascriptBridge.callHandler('locationClick',function(response) {
        alert(response);
        document.getElementById("returnValue").value = response;
    });
}

// 沒有引數,又不需要回撥可以這樣寫
function shake() {
    WebViewJavascriptBridge.callHandler('shakeClick');
}
複製程式碼

至此,JS 通過WebViewJavascriptBridge呼叫Native 的功能就完成了。

第六步,Native 呼叫 JS 功能。

Native 呼叫js 功能與 js 呼叫Native 的原理和流程一樣。 1、現在js 中註冊,Native 要呼叫的功能。 2、Native 呼叫註冊時,該功能的別名,就可以完成呼叫。 在js 中註冊 Native 要呼叫的功能,同樣需要為該功能設定一個別名HandlerName。 其實這個步驟在前面介紹過,程式碼如下:

setupWebViewJavascriptBridge(function(bridge) {

      // 這裡註冊Native 要呼叫的js 功能。
     bridge.registerHandler('testJSFunction', function(data, responseCallback) {
        alert('JS方法被呼叫:'+data);
        responseCallback('js執行過了');
     })
     // 如果要有其他Native 呼叫的js 功能,在這裡按照上面的格式新增。
})
複製程式碼

上述程式碼,是在JS 中註冊了一個別名叫testJSFunction的js功能,第二個引數是一個function。function裡的data ,就是Native 呼叫該功能時傳過來的引數,responseCallback是執行完js 程式碼後,通過responseCallback將必要的資訊返回到Native中。

Native 呼叫js 裡註冊的功能,示例程式碼:

- (void)rightClick
{
    //    // 如果不需要引數,不需要回撥,使用這個
    //    [_webViewBridge callHandler:@"testJSFunction"];
    //    // 如果需要引數,不需要回撥,使用這個
    //    [_webViewBridge callHandler:@"testJSFunction" data:@"一個字串"];
    // 如果既需要引數,又需要回撥,使用這個
    [_webViewBridge callHandler:@"testJSFunction" data:@"一個字串" responseCallback:^(id responseData) {
        NSLog(@"呼叫完JS後的回撥:%@",responseData);
    }];
}
複製程式碼

WKWebView 通過WebViewJavascriptBridge實現js 與Native 的互動,到這裡就已經完成了。 這是工程示例中的效果圖:

示例工程地址:JS_OC_WebViewJavascriptBridge

Have Fun!

相關文章