本文的實(shí)例demo
demo1
webVIew和WKWebview的使用區(qū)別
demo2
第三方框架WebViewJavascriptBridge是支持到iOS6之前的版本的企锌,用于支持native的iOS與javascript交互非迹。如果需要支持到iOS6之前的app抢腐,使用它是很不錯(cuò)的。本篇講講WebViewJavascriptBridge的基本原理及詳細(xì)講講如何去使用猜憎,包括iOS端的使用和JS端的使用东抹。
經(jīng)過多番百度爆阶、Google,發(fā)現(xiàn)WebViewJavascriptBridge的資源講解不是翻譯官方文檔就是直接說官方提供的demo溶褪。但是筆者在寫這個(gè)demo時(shí)也遇到了不少問題甘耿,也想看看大家是怎么使用的,特別是JS端竿滨,弄了好久都沒有回調(diào)佳恬,原來是因?yàn)閘og。
寫下本篇文章于游,希望大家少走彎路吧毁葱!
與H5交互的方案
縱觀所有iOS與H5交互的方案,有以下幾種:
第一種:有很多的app直接使用在webview的代理中通過攔截的方式與native進(jìn)行交互贰剥,通常是通過攔截url scheme判斷是否是我們需要攔截處理的url及其所對(duì)應(yīng)的要處理的功能是什么倾剿。任意版本都支持。
第二種:iOS7之后出了JavaScriptCore.framework用于與JS交互蚌成,但是不支持iOS6前痘,對(duì)于還需要支持iOS6的app,就不能考慮這個(gè)了担忧。若需要了解芹缔,看最后的推薦閱讀。
第三種:WebViewJavascriptBridge開源庫使用瓶盛,本質(zhì)上最欠,它也是通過webview的代理攔截scheme示罗,然后注入相應(yīng)的JS。
第四種:react-native芝硬,這個(gè)沒玩過(與前三種不同)蚜点。
本篇文章專講講WebViewJavascriptBridge。
基本原理
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
if (webView != _webView) { return YES; }
NSURL *url = [request URL];
__strong WVJB_WEBVIEW_DELEGATE_TYPE* strongDelegate = _webViewDelegate;
if ([_base isCorrectProcotocolScheme:url]) {
if ([_base isBridgeLoadedURL:url]) {
[_base injectJavascriptFile];
} else if ([_base isQueueMessageURL:url]) {
NSString *messageQueueString = [self _evaluateJavascript:[_base webViewJavascriptFetchQueyCommand]];
[_base flushMessageQueue:messageQueueString];
} else {
[_base logUnkownMessage:url];
}
return NO;
} else if (strongDelegate && [strongDelegate respondsToSelector:@selector(webView:shouldStartLoadWithRequest:navigationType:)]) {
return [strongDelegate webView:webView shouldStartLoadWithRequest:request navigationType:navigationType];
} else {
return YES;
}
}
在攔截后拌阴,通過先通過-isBridgeLoadedURL:方法判斷URL是否是需要bridge的URL绍绘,若是,則通過injectJavascriptFile方法注入JS迟赃;否則判斷URL是否是隊(duì)列消息脯倒,若是,則執(zhí)行查詢命令JS并刷新消息隊(duì)列捺氢;最后藻丢,URL被識(shí)別為未知的消息。
JS端如何使用
下面是本demo的HTML完整代碼:
<!doctype html>
<html>
<head>
<meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1.0, maximum-scale=1.0">
<style type='text/css'>
html { font-family:Helvetica; color:#222; }
h1 { color:steelblue; font-size:24px; margin-top:24px; }
button { margin:0 3px 10px; font-size:12px; }
.logLine { border-bottom:1px solid #ccc; padding:4px 2px; font-family:courier; font-size:11px; }
</style>
</head>
<body>
<h1>WebViewJavascriptBridge Demo</h1>
<script>
window.onerror = function(err) {
log('window.onerror: ' + err)
}
/*這段代碼是固定的摄乒,必須要放到j(luò)s中*/
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)
}
/*與OC交互的所有JS方法都要放在此處注冊悠反,才能調(diào)用通過JS調(diào)用OC或者讓OC調(diào)用這里的JS*/
setupWebViewJavascriptBridge(function(bridge) {
var uniqueId = 1
function log(message, data) {
var log = document.getElementById('log')
var el = document.createElement('div')
el.className = 'logLine'
el.innerHTML = uniqueId++ + '. ' + message + ':<br/>' + JSON.stringify(data)
if (log.children.length) {
log.insertBefore(el, log.children[0])
} else {
log.appendChild(el)
}
}
/* Initialize your app here */
/*我們在這注冊一個(gè)js調(diào)用OC的方法,不帶參數(shù)馍佑,且不用ObjC端反饋結(jié)果給JS:打開本demo對(duì)應(yīng)的博文*/
bridge.registerHandler('openWebviewBridgeArticle', function() {
log("openWebviewBridgeArticle was called with by ObjC")
})
/*JS給ObjC提供公開的API斋否,在ObjC端可以手動(dòng)調(diào)用JS的這個(gè)API。接收ObjC傳過來的參數(shù)拭荤,且可以回調(diào)ObjC*/
bridge.registerHandler('getUserInfos', function(data, responseCallback) {
log("Get user information from ObjC: ", data)
responseCallback({'userId': '123456', 'blog': '標(biāo)哥的技術(shù)博客'})
})
/*JS給ObjC提供公開的API茵臭,ObjC端通過注冊,就可以在JS端調(diào)用此API時(shí)舅世,得到回調(diào)旦委。ObjC端可以在處理完成后,反饋給JS雏亚,這樣寫就是在載入頁面完成時(shí)就先調(diào)用*/
bridge.callHandler('getUserIdFromObjC', function(responseData) {
log("JS call ObjC's getUserIdFromObjC function, and js received response:", responseData)
})
document.getElementById('blogId').onclick = function (e) {
log('js call objc: getBlogNameFromObjC')
bridge.callHandler('getBlogNameFromObjC', {'blogURL': 'http://www.huangyibiao.com'}, function(response) {
log('JS got response', response)
})
}
})
</script>
<div id='buttons'></div> <div id='log'></div>
<div>
<input type="button" value="getBlogNameFromObjC" id="blogId"/>
</div>
</body>
</html>
在JS端缨硝,嵌入步驟是:
第一步:將下面的代碼放在JS中:
/*這段代碼是固定的,必須要放到j(luò)s中*/
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)
}
這段代碼是固定的罢低,必須要放到j(luò)s中
第二步:在下面的方法體里寫相關(guān)JS代碼:
setupWebViewJavascriptBridge(function(bridge) {
/* Initialize your app here */
所有與iOS交互的JS代碼放這里查辩!
})
JS調(diào)用iOS代碼
通過bridge.callHandler來調(diào)用:
bridge.callHandler('getBlogNameFromObjC',
{'blogURL': 'http://www.huangyibiao.com'},
function callback(response) {
log('JS got response', response)
}
})
其中,各參數(shù)說明如下:
getBlogNameFromObjC:是iOS端register的handleName网持,在iOS端注冊后宜岛,JS就可以直接通過這個(gè)handleName與iOS交互。比如功舀,當(dāng)點(diǎn)擊某個(gè)按鈕時(shí)萍倡,就執(zhí)行上面這一段代碼,結(jié)果就是js端將參數(shù)傳給了iOS端日杈,iOS端收到參數(shù)遣铝,然后通過回調(diào)給js,js會(huì)收到response莉擒,然后打印出來
{‘blogURL’: ‘http://www.huangyibiao.com’}:這是JSON字符串酿炸,傳到iOS端會(huì)被WebViewJavascriptBridge自動(dòng)轉(zhuǎn)換成id對(duì)象,然后在回調(diào)處看到的就是字典對(duì)象了涨冀。
callback:這是個(gè)js函數(shù)名填硕,在iOS端收到回調(diào)后,拿到了參數(shù)鹿鳖,然后通過閉包回調(diào)反饋給js端扁眯,這個(gè)反饋就是通過callback函數(shù)來傳值給js。
JS端加入WebViewJavascriptBridge代碼注意事項(xiàng)
如果在下面的函數(shù)體內(nèi)有任何錯(cuò)誤翅帜,都不會(huì)有打印日志姻檀,也不會(huì)有任何回調(diào):
setupWebViewJavascriptBridge(function(bridge) {
/* Initialize your app here */
所有與iOS交互的JS代碼放這里!
})
因此涝滴,如果遇到什么也沒有輸出绣版,說明你寫錯(cuò)了。另外歼疮,上面有demo中杂抽,log函數(shù)是自定義的,不是系統(tǒng)的韩脏,因此如果沒有加入這個(gè)函數(shù)的定義缩麸,調(diào)用它也會(huì)導(dǎo)致不能交互。
iOS端如何使用
第一步:開啟日志
// 開啟日志赡矢,方便調(diào)試
[WebViewJavascriptBridge enableLogging];
第二步:給ObjC與JS建立橋梁
// 給哪個(gè)webview建立JS與OjbC的溝通橋梁
self.bridge = [WebViewJavascriptBridge bridgeForWebView:webView];
// 設(shè)置代理杭朱,如果不需要實(shí)現(xiàn),可以不設(shè)置
[self.bridge setWebViewDelegate:self];
第三步:注冊HandleName吹散,用于給JS端調(diào)用iOS端
// JS主動(dòng)調(diào)用OjbC的方法
// 這是JS會(huì)調(diào)用getUserIdFromObjC方法痕檬,這是OC注冊給JS調(diào)用的
// JS需要回調(diào),當(dāng)然JS也可以傳參數(shù)過來送浊。data就是JS所傳的參數(shù)梦谜,不一定需要傳
// OC端通過responseCallback回調(diào)JS端,JS就可以得到所需要的數(shù)據(jù)
[self.bridge registerHandler:@"getUserIdFromObjC" handler:^(id data, WVJBResponseCallback responseCallback) {
NSLog(@"js call getUserIdFromObjC, data from js is %@", data);
if (responseCallback) {
// 反饋給JS
responseCallback(@{@"userId": @"123456"});
}
}];
[self.bridge registerHandler:@"getBlogNameFromObjC" handler:^(id data, WVJBResponseCallback responseCallback) {
NSLog(@"js call getBlogNameFromObjC, data from js is %@", data);
if (responseCallback) {
// 反饋給JS
responseCallback(@{@"blogName": @"標(biāo)哥的技術(shù)博客"});
}
}];
第四步:直接調(diào)用JS端注冊的HandleName
[self.bridge callHandler:@"getUserInfos" data:@{@"name": @"標(biāo)哥"} responseCallback:^(id responseData) {
NSLog(@"from js: %@", responseData);
}];