需求:
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)


二、導(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ì)的注釋。