WKWebView-Bridge 在react-native封裝的實現(xiàn) iOS開發(fā)

需求:

1、監(jiān)控與重寫url跳轉(zhuǎn)。

2、自定義Loading樣式不夠強大。

3、RN與Web通信的Bridge。

4、一些特殊應(yīng)用:tel://,mailto://,微信的,甚至于一些自定義協(xié)議。

5、將來做自己的WebAPI。


實現(xiàn)

一、文件結(jié)構(gòu)

OC文件示意圖.png
js文件示意圖.png

二、導(dǎo)入說明

參考鏈接:

UIWebView-bridge:GItHub鏈接

WKWebView:GitHub鏈接;

上述兩個示例,一個可以實現(xiàn)RN與Web的通信,另一個無法通信卻可以實現(xiàn)對加載進(jìn)度的監(jiān)控,方便我們實現(xiàn)自己的loading.

導(dǎo)入方式我們選擇WKWebView的方式導(dǎo)入(項目中我已經(jīng)導(dǎo)入完畢);
然后在導(dǎo)入的源代碼里加入我們要實現(xiàn)的Bridge功能即可。

三、使用說明

1、監(jiān)控與重寫URL跳轉(zhuǎn)
    /*
     *Allows custom handling of any webview requests by a JS handler. Return true
     *or false from this method to continue loading the request.
     *@platform ios
     */
    onShouldStartLoadWithRequest: PropTypes.func,

這個方法是webView的一個屬性,可以綁定一個函數(shù),例如:

<WKWebView  source={{uri:this.state.src}}
                    ref = {'webviewref'}
                    startInLoadingState={true}
                    renderLoading={()=><Text>正在加載頁面...</Text>}
                    style={styles.webViewStyle}
                    onLoad = {()=>TestMBProgressManager.textExample("加載中.....")}
                    onShouldStartLoadWithRequest  = {(e)=>{
                      console.log(e);
                      return true;
                    }}
                    injectedJavaScript = {injectScript}
                    onBridgeMessage = { this.onBridgeMessage.bind(this) }
        />

在這個函數(shù)里我們就可以對當(dāng)前加載的URL進(jìn)行篩選和判斷。

輸出的日志示例如下:

{ target: 9,
canGoBack: false,
lockIdentifier: 1189458900,
loading: false,
title: '百度一下',
canGoForward: false,
 navigationType: -1,
  url: 'wkwvb://message1473669712719' }```

在獲取到URL之后,返回false就是不讓加載當(dāng)前URL。

若是想跳轉(zhuǎn)新的頁面,只需重新設(shè)置soruce并reload即可。

###### 2、自定義Loading樣式不夠強大

在這里提供了多種方式去自定義loading。

(1)在react里自定義視圖,設(shè)置樣式。
  
  示例如下:

//WebView的屬性之一,返回一個視圖,在加載的時候呈現(xiàn)。
renderLoading={()=><Text>正在加載頁面...</Text>}

(2) 通過native實現(xiàn)。

 在這里我創(chuàng)建了一個MBPrograssHUD的管理類,可以實現(xiàn)自定義文字、圖片等的 加載圖。在開始加載的時候,使他顯示,加載完畢后,使其消失。

但不建議使用這種方法去實現(xiàn)。

###### 3、RN與Web通信的Bridge

我們用一個簡單的打招呼的過程來理解Bridge的使用(這僅僅是一個示例,更復(fù)雜的邏輯,根據(jù)需求由我們的工程師自己去實現(xiàn)吧。)
首先在建立WebView的時候,我們?yōu)楫?dāng)前頁面注入如下js代碼,由Web向RN打招呼:(reactive中實現(xiàn))

const injectScript = `
(function(){
if (WebViewBridge) {

    WebViewBridge.onMessage = function(message){
      if (message === "hello from react-native") {
        WebViewBridge.send("got the message inside webview");
      }
    };

    WebViewBridge.send("hello from webview");
    }

}());

`;



在native的RCTWebView.m中,我實現(xiàn)了以下方法:

//since there is no easy way to load the static lib resource in ios,
//we are loading the script from this method.

  • (NSString*)webViewBridgeScript{

    return NSStringMultiline((function (window) {
    'use strict';

    //Make sure that if WebViewBridge already in scope we don't override it.
    if (window.WebViewBridge) {
    return;
    }

    var RNWBSchema = 'wkwvb';
    var sendQueue = [];
    var receiveQueue = [];
    var doc = window.document;
    var customEvent = doc.createEvent('Event');

    function callFunc(func, message) {
    if ('function' === typeof func) {
    func(message);
    }
    }

    function signalNative() {
    window.location = RNWBSchema + '://message' + new Date().getTime();
    }

    //I made the private function ugly signiture so user doesn't called them accidently.
    //if you do, then I have nothing to say. :(
    var WebViewBridge = {
    //this function will be called by native side to push a new message
    //to webview.
    push: function (message) {
    receiveQueue.push(message);
    //reason I need this setTmeout is to return this function as fast as
    //possible to release the native side thread.
    setTimeout(function () {
    var message = receiveQueue.pop();
    callFunc(WebViewBridge.onMessage, message);
    }, 15); //this magic number is just a random small value. I don't like 0.
    },
    fetch: function () {
    //since our sendQueue array only contains string, and our connection to native
    //can only accept string, we need to convert array of strings into single string.
    var messages = JSON.stringify(sendQueue);

    //we make sure that sendQueue is resets
    sendQueue = [];
    
    //return the messages back to native side.
    return messages;
    

    },
    //make sure message is string. because only string can be sent to native,
    //if you don't pass it as string, onError function will be called.
    send: function (message) {
    alert(message);
    if ('string' !== typeof message) {
    callFunc(WebViewBridge.onError, "message is type '" + typeof message + "', and it needs to be string");
    return;
    }

    //we queue the messages to make sure that native can collects all of them in one shot.
    sendQueue.push(message);
    //signal the objective-c that there is a message in the queue
    signalNative();
    

    },
    onMessage: null,
    onError: null
    };

    window.WebViewBridge = WebViewBridge;

    //dispatch event
    customEvent.initEvent('WebViewBridge', true, true);
    doc.dispatchEvent(customEvent);
    }(window));
    );
    }


這是一段js代碼,在每次加載完畢后,都為當(dāng)前應(yīng)用注入,旨在建立web與RN的通信通道。

在RN中,我們監(jiān)聽Web消息的方法如下:

onBridgeMessage(message) {

  // var webview = this.refs.webview.getDOMNode();
  // const { webviewref } = this.refs;
  const webview = this.refs['webviewref']
  ;
  switch (message) {
    case "hello from webview":
    webview.sendToBridge("hello from react-native");
    console.log('我們打招呼給WebView');
    break;
    case "got the message inside webview":
    console.log("we have got a message from webview!yeah!");
    break;
  }

}
//綁定到WebView的屬性上面
onBridgeMessage = { this.onBridgeMessage.bind(this) }

每當(dāng)web有消息發(fā)送過來的時候,這個方法都會被觸發(fā),然后我們就可以在RN里面根據(jù)我們自己的需求去處理相應(yīng)的消息。

這里通過sendToBridge的方法,由RN向web發(fā)送消息。

然后又由注入的js代碼中的WebViewBridge.onMessage這個函數(shù)接受消息,并做處理。(即我們最開始注入的js代碼)。

這樣,就實現(xiàn)了Bridge的通信。

###### 4、一些特殊應(yīng)用:tel://,mailto://,微信的,甚至于一些自定義協(xié)議。

在native代碼中,有這樣一個方法:
  • (void)webView:(__unused WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler

在這個方法中:

NSURLRequest request = navigationAction.request;
NSURL
url = request.URL;
NSString* scheme = url.scheme;

 
這樣就可以根據(jù)不同的scheme對不同的協(xié)議進(jìn)行處理了。包括上述bridge,在這里也運用了scheme去實現(xiàn)的,部分代碼:

if (isJSNavigation) {
decisionHandler(WKNavigationActionPolicyCancel);
}
else if (navigationAction.targetFrame && ([scheme isEqualToString:@"http"] || [scheme isEqualToString:@"https"])) {
decisionHandler(WKNavigationActionPolicyAllow);
}
else {
if([scheme isEqualToString:@"wkwvb"]){
decisionHandler(WKNavigationActionPolicyCancel);
return;
}

if (![scheme isEqualToString:@"about"]) {
  [[UIApplication sharedApplication] openURL:url];
}
decisionHandler(WKNavigationActionPolicyAllow);

}


在這里wkwvb就是RN與web互相通信時,我自定義的協(xié)議,如果有其他的協(xié)議,都可以在這個方法中進(jìn)行處理。具體的邏輯,就根據(jù)需求由工程師去實現(xiàn)了。

###### 5、將來做自己的WebAPI

具體邏輯,在將來,根據(jù)需求由工程師去實現(xiàn)。

#如何使用這個控件,請詳細(xì)閱讀wkwebView.ios.js文件,每個屬性的用法和功能都有詳細(xì)的注釋。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

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