在移動端開發(fā)中,有時可能考慮到開發(fā)效率,會在原生應(yīng)用中集成Web界面,這樣只需要web開發(fā)人員寫完之后,原生只需要加載出來就可以了。但是,也存在native和js之間的交互情況,下面是我在解決交互中使用的方法,和遇見的問題。
在原生用加載web界面,在iOS8之后,我們有兩種選擇,一種是UIKit里的UIWebView,一種是WebKit的WKWebView,其中WKWebView是在iOS8之后才推出的新的web展示控件,目的也很明確,就是為了解決UIWebView加載速度慢,消耗內(nèi)存高的問題,據(jù)說同時加載100個頁面,WKWebView內(nèi)存的消耗量,要比UIWebView少很多。相對而言,如果不支持iOS8之前的版本的話,選擇WKWebView可能會更好一點。
閑話少說,上代碼,Demo中含有兩種方式,一種不借助三方庫,直接用WKWebView,一種是借助三方庫。另外,本篇只是講了一些native和Javascript之間的交互問題,對于WKWebView的使用,會在以后的文章中繼續(xù)更新。demo地址
方式一: 不借助三方庫,直接使用WKWebView的相關(guān)代理,進(jìn)行交互。(參考demo中Native文件夾)
-
html代碼(參考demo中Native.html文件)
<html>
<!--描述網(wǎng)頁信息-->
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>WKWebViewDemo</title>
<style>
*{
font-size: 50px;
}
.btn{height:80px; width:80%; padding: 0px 30px; background-color: #0071E7; border: solid 1px #0071E7; border-radius:5px; font-size: 1em; color: white}
</style>
<script src="Native.js"></script>
</head>
<!--網(wǎng)頁具體內(nèi)容-->
<body>
<div>
<button class="btn" type="button" onclick="btnClick()">點我調(diào)用OC</button>
</div>
<br/>
<div id="div1"></div>
</body>
</html>
-
native調(diào)用javascript
- 1.JS中注冊要被OC調(diào)用的方法
//JS響應(yīng)OC的調(diào)用 function OCCallJS(msg){ document.getElementById('div1').innerText = msg ? msg : '收到了來自O(shè)C的調(diào)用' //如果OC需要在調(diào)用了js之后,獲取返回值,可以通過return return 'JS收到了OC的調(diào)用,并返回了我' }- 2.OC調(diào)用JS中已經(jīng)注冊的方法
-(void)click { //OC 主動調(diào)用JS [_wkWebView evaluateJavaScript:@"OCCallJS('JS被調(diào)用了')" completionHandler:^(id _Nullable response, NSError * _Nullable error) { //response是js方法OCCallJS的返回值 NSLog(@"response:%@,error:%@",response,error); }]; }
-
javascript調(diào)用native
- 1.在
OC中注冊JS需要調(diào)用的方法
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init]; self.wkWebView = [[WKWebView alloc] initWithFrame:CGRectMake(0, 20, CGRectGetWidth(self.view.bounds), CGRectGetHeight(self.view.bounds)-120) configuration:config]; self.wkWebView.navigationDelegate = self; [self.view addSubview:self.wkWebView]; //注冊方法,必須在HTML加載之前添加 WKUserContentController *userCC = self.wkWebView.configuration.userContentController; //JSCallOC,是javascript 主動調(diào)用OC時, [userCC addScriptMessageHandler:self name:@"JSCallOC"]; [self loadLocalHtmlWithFileName:@"Native"];- 2.
JS調(diào)用在OC中注冊過的方法
function btnClick() { /** * JS 調(diào)用 OC,同樣也需要現(xiàn)在OC注冊方法名,然后才能在JS中調(diào)用 * 原型:window.webkit.messageHandlers.<name>.postMessage(<messageBody>) */ window.webkit.messageHandlers.JSCallOC.postMessage('JS主動調(diào)用OC') } - 1.在
方式二: 借助三方庫,推薦使用 WebViewJavascriptBridge(參考demo中JSBridge文件夾)
-
html代碼(參考demo中JSBridge.html文件)
<html>
<!--描述網(wǎng)頁信息-->
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>WKWebViewDemo</title>
<style>
*{
font-size: 50px;
}
.btn{height:80px; width:80%; padding: 0px 30px; background-color: #0071E7; border: solid 1px #0071E7; border-radius:5px; font-size: 1em; color: white}
</style>
<script src="JSBridge.js"></script>
</head>
<!--網(wǎng)頁具體內(nèi)容-->
<body>
<div>
<button class="btn" type="button" onclick="btnClick()">點我調(diào)用OC</button>
</div>
<br/>
<div id="div1"></div>
</body>
</html>
-
native調(diào)用javascript
-
1.
JS中注冊要被OC調(diào)用的方法- 首先在
JSBridge.js定義一個全局可用的方法變量
//這部分是固定的 var iosBridge = function (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 = 'https://__bridge_loaded__'; document.documentElement.appendChild(WVJBIframe); setTimeout(function() { document.documentElement.removeChild(WVJBIframe) }, 0) }-
在
JSBridge.js中注冊提供給OC調(diào)用的方法。OC調(diào)用JS中的注冊方法,JS有兩種響應(yīng)方式,分別為:
(1)JS接收到OC的調(diào)用請求,需要通過回調(diào)把一些數(shù)據(jù)給OC的/** *方法原型:bridge.registerHandler("handlerName", function(data,responseData) { ... }) *@first param: 'OCCallJS', 是OC調(diào)用JS的方法名,需要事先注冊 *@second param: 回調(diào)函數(shù),其中data 是OC通過 ‘OCCallJS’ 方法,傳過來的值, ’responseCallBack‘ 是JS回調(diào)給OC的方法,通過’responseCallBack‘可以給OC傳遞一個值 */ bridge.registerHandler('OCCallJS', function(data,responseCallBack){ console.log('js is called by oc,data from oc:' + data) document.getElementById('div1').innerText = data + '并準(zhǔn)備向JS要一個橘子' responseCallBack('并向JS要一個橘子') })(2)
JS接收到OC的調(diào)用請求,無需回調(diào)傳值的,分為需要傳參,和不需要傳參兩種,當(dāng)然你也可以直接把不需要的部分傳nil/** *JS 方法原型:bridge.registerHandler("handlerName", function(responseData) { ... }) *@first param: 'OCCallJS', 是OC調(diào)用JS的方法名,需要事先注冊 *@second param: 回調(diào)函數(shù),其中data 是OC通過 ‘OCCallJS’ 方法,傳過來的值,可能為null */ bridge.registerHandler('OCCallJS', function (data){ document.getElementById('div1').innerText = data ? data : 'Objective-C主動給JS一個蘋果,并且不要回報' })
- 首先在
-
2.
OC調(diào)用JS中已經(jīng)注冊的方法(OC中調(diào)用方法,必須要和JS中注冊的方法參數(shù)相匹配,否則會報錯)。OC端方法原型有三種://1 [bridge callHandler:(NSString*)handlerName data:(id)data responseCallback:(WVJBResponseCallback)callback]; //2 [bridge callHandler:(NSString)handlerName data:(id)data //3 [bridge callHandler:(NSString)handlerName](1) 針對于
JS注冊方法1/**注冊方法1必須對應(yīng)`OC`中的方法,如果`OC`沒有`callback`,`JS`端會報錯. *OC方法原型:[bridge callHandler:(NSString*)handlerName data:(id)data responseCallback:(WVJBResponseCallback)callback] *@first param: 'OCCallJS', 是OC調(diào)用的JS方法名 *@second param data: 是需要通過OCCallJS方法,傳遞給JS的數(shù)據(jù) *@third param responseCallback: JS 收到調(diào)用之后,回調(diào)給OC的值 */ [self.bridge callHandler:@"OCCallJS" data:@"Objective-C主動給JS一個蘋果" responseCallback:^(id responseData) NSLog(@"Objective-C給JS一個蘋果后,%@",responseData); self.label.text = [NSString stringWithFormat:@"Objective-C給JS一個蘋果后,%@",responseData]; }];(2) 針對于
JS注冊方法2/** *此方法對應(yīng)OC中下面兩個方法 *OC方法原型:[bridge callHandler:(NSString*)handlerName data:(id)data] or [bridge callHandler:(NSString*)handlerName] *如果是前者 則對應(yīng)的JS回調(diào)函數(shù)data中有值,后者,則為null */ [self.bridge callHandler:@"OCCallJS" data:@"Objective-C主動給JS一個蘋果,并且不要回報"]; 或者 [self.bridge callHandler:@"OCCallJS"]; -
javascript調(diào)用native
- 1.在OC中注冊JS需要調(diào)用的方法
/**js 調(diào)用原生,獲取數(shù)據(jù),通過回調(diào)給JS *OC方法原型- (void)registerHandler:(NSString *)handlerName handler:(WVJBHandler)handler *@first param handlerName: 'JSCallOC', 是JS調(diào)用的OC方法名 *@second param handler: JS調(diào)用OC的回調(diào),包括JS傳過來的數(shù)據(jù)data,和OC給JS回調(diào)傳值。 */ [self.bridge registerHandler:@"JSCallOC" handler:^(id data, WVJBResponseCallback responseCallback) { //其中data可能為nil,如果不想在此給JS傳值,可responseCallback(nil); self.label.text = [NSString stringWithFormat:@"%@,并準(zhǔn)備向Objective-C要一個蘋果",data]; responseCallback(@"并向Objective-C要了一個蘋果"); }]; - 2.JS 調(diào)用在OC中注冊過的方法
同樣JS調(diào)用OC的原型方法,也有三種
下面的三種方法實例,分別對應(yīng)上面的三種方法//1 bridge.callHandler("handlerName") //2 bridge.callHandler("handlerName", data) //3 bridge.callHandler("handlerName", data, function responseCallback(responseData)//1 bridge.callHandler('JSCallOC') //2 bridge.callHandler('JSCallOC','JS主動給Objective-C一個橘子') //3,如果OC的注冊方法中,有回調(diào)函數(shù)中有值,則必須采用此方法,否則js會報錯 bridge.callHandler('JSCallOC','JS主動給Objective-C一個橘子',function(responseData){ document.getElementById('div1').innerText = 'JS主動給Objective-C一個橘子,' + responseData })
- 1.在OC中注冊JS需要調(diào)用的方法
注意點
- 不管是原生還是用三方庫,
native需要調(diào)用的javascript的方法,貌似必須要在HTML中提前注冊,尤其是單頁面應(yīng)用,因為單頁面應(yīng)用只有一個HTML,界面基本上都組件化渲染。 -
javascript調(diào)用原生時,同樣也需要在調(diào)用前,需要先注冊。