WKWebView-Bridge 在react-native封装的实现 iOS开发

需求:

1、监控与重写url跳转。

2、自定义Loading样式不够强大。

3、RN与Web通信的Bridge。

4、一些特殊应用:tel://,mailto://,微信的,甚至于一些自定义协议。

5、将来做自己的WebAPI。


实现

一、文件结构

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

二、导入说明

参考链接:

UIWebView-bridge:GItHub链接;

WKWebView:GitHub链接;

上述两个示例,一个可以实现RN与Web的通信,另一个无法通信却可以实现对加载进度的监控,方便我们实现自己的loading.

导入方式我们选择WKWebView的方式导入(项目中我已经导入完毕);
然后在导入的源代码里加入我们要实现的Bridge功能即可。

三、使用说明

1、监控与重写URL跳转
    /*
     *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的一个属性,可以绑定一个函数,例如:

正在加载页面...}
                    style={styles.webViewStyle}
                    onLoad = {()=>TestMBProgressManager.textExample("加载中.....")}
                    onShouldStartLoadWithRequest  = {(e)=>{
                      console.log(e);
                      return true;
                    }}
                    injectedJavaScript = {injectScript}
                    onBridgeMessage = { this.onBridgeMessage.bind(this) }
        />

在这个函数里我们就可以对当前加载的URL进行筛选和判断。

输出的日志示例如下:

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

在获取到URL之后,返回false就是不让加载当前URL。

若是想跳转新的页面,只需重新设置soruce并reload即可。

###### 2、自定义Loading样式不够强大

在这里提供了多种方式去自定义loading。

(1)在react里自定义视图,设置样式。
  
  示例如下:

//WebView的属性之一,返回一个视图,在加载的时候呈现。
renderLoading={()=>正在加载页面...}

(2) 通过native实现。

 在这里我创建了一个MBPrograssHUD的管理类,可以实现自定义文字、图片等的 加载图。在开始加载的时候,使他显示,加载完毕后,使其消失。

但不建议使用这种方法去实现。

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

我们用一个简单的打招呼的过程来理解Bridge的使用(这仅仅是一个示例,更复杂的逻辑,根据需求由我们的工程师自己去实现吧。)
首先在建立WebView的时候,我们为当前页面注入如下js代码,由Web向RN打招呼:(reactive中实现)

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中,我实现了以下方法:

//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代码,在每次加载完毕后,都为当前应用注入,旨在建立web与RN的通信通道。

在RN中,我们监听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) }

每当web有消息发送过来的时候,这个方法都会被触发,然后我们就可以在RN里面根据我们自己的需求去处理相应的消息。

这里通过sendToBridge的方法,由RN向web发送消息。

然后又由注入的js代码中的WebViewBridge.onMessage这个函数接受消息,并做处理。(即我们最开始注入的js代码)。

这样,就实现了Bridge的通信。

###### 4、一些特殊应用:tel://,mailto://,微信的,甚至于一些自定义协议。

在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;

 
这样就可以根据不同的scheme对不同的协议进行处理了。包括上述bridge,在这里也运用了scheme去实现的,部分代码:

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互相通信时,我自定义的协议,如果有其他的协议,都可以在这个方法中进行处理。具体的逻辑,就根据需求由工程师去实现了。

###### 5、将来做自己的WebAPI

具体逻辑,在将来,根据需求由工程师去实现。

#如何使用这个控件,请详细阅读wkwebView.ios.js文件,每个属性的用法和功能都有详细的注释。

你可能感兴趣的:(WKWebView-Bridge 在react-native封装的实现 iOS开发)