iOS下JS與OC互相調用(六)--WKWebView + WebViewJavascriptBridge


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

創建WKWebView。

這一步,唯一需要注意的地方,就是不用再設置WKWebView 的navigationDelegate,下一步你就知道為什么了

- (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];

上一步說了不用再設置WKWebView 的navigationDelegate,那是因為在{-bridgeForWebView:}內已經將WKWebView 的navigationDelegate設置為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 = @"廣東省深圳市";
        // 將結果返回給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)
}

然后在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 的交互,到這里就已經完成了。

demo地址: https://github.com/domanc/JS_OC_WebViewJavascriptBridge_UIWebView.git

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM