在HTML添加交互代碼
<!-- script 嵌入JS代碼 -->
window.onerror = function(err) {
log('window.onerror: ' + err)
}
/*這段代碼是固定的圾旨,必須要放到js中*/
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';
//src是js交互的標識碼
WVJBIframe.src = 'kadios://__KAD_BRIDGE_LOADED__';
document.documentElement.appendChild(WVJBIframe);
//加載這個方法后就刪除自定義的src 讓后面重定向url
//把iframe一起干掉,既然改變src不會刷新頁面是整,重新創(chuàng)建一個iframe 就會刷新
setTimeout(function() { document.documentElement.removeChild(WVJBIframe) }, 0)
}
/*與OC交互的所有JS方法都要放在此處注冊瞧剖,才能調用通過JS調用OC或者讓OC調用這里的JS*/
setupWebViewJavascriptBridge(function(bridge) {
//JS 調用 OC方法 callHandler
document.getElementById("showButton").onclick = function(e){
bridge.callHandler('goHome:', {'name': 'lemon' + Math.random()}, function(response) {
})
}
/*JS給ObjC提供公開的API拭嫁,在ObjC端可以手動調用JS的這個API。接收ObjC傳過來的參數(shù)抓于,且可以回調ObjC*/
//OC 調用JS registerHandler
bridge.registerHandler('getUserInfos', function(data, responseCallback) {
document.getElementById("changeTitle").innerHTML = data;
//回調給ObjC
responseCallback({'userId': '123456', 'title': 'Hello World!'})
})
})
app中使用WebViewJavascriptBridge的代碼
//創(chuàng)建WebViewJavascriptBridge做為屬性
@property (nonatomic, strong) WebViewJavascriptBridge *bridge;
//給webView建立JS與OC橋梁
self.bridge = [WebViewJavascriptBridge bridgeForWebView:self.webView];
//設置代理
[self.bridge setWebViewDelegate:self ];
// 1.JS主動調用OjbC的方法
// 這是JS會調用getUserIdFromObjC方法做粤,這是OC注冊給JS調用的
// JS需要回調,當然JS也可以傳參數(shù)過來毡咏。data就是JS所傳的參數(shù)驮宴,不一定需要傳
// OC端通過responseCallback回調JS端,JS就可以得到所需要的數(shù)據
[self.bridge registerHandler:@"goHome:" handler:^(id data, WVJBResponseCallback responseCallback) {
self.showDataLB.text = [NSString stringWithFormat:@"%@",data];
[self.showDataLB sizeToFit];
NSLog(@"goHome:%@",data);
}];
-(void) clickShowButton{
//OC調用了js方法
[self.bridge callHandler:@"getUserInfos" data:@"我點擊了showButton" responseCallback:^(id responseData) {
//data是js的回調數(shù)據
NSLog(@"%@",responseData);
}];
}
上面的代碼是WebViewJavascriptBridge的基本使用呕缭。
下面是關于WebViewJavascriptBridge的原理
WebViewJavascriptBridge類的作用是綁定webView堵泽,在該類中處理WebView的代理。
js調用OC的原理
+ (instancetype)bridgeForWebView:(WVJB_WEBVIEW_TYPE*)webView {
WebViewJavascriptBridge* bridge = [[self alloc] init];
[bridge _platformSpecificSetup:webView];
return bridge;
}
- (void) _platformSpecificSetup:(WVJB_WEBVIEW_TYPE*)webView {
_webView = webView;
_webView.delegate = self;
_base = [[WebViewJavascriptBridgeBase alloc] init];
//base實例會把該注冊事件放進到base的一個消息池子(負責接受多個OC注冊事件)中恢总,方便后續(xù)處理
_base.delegate = self;
}
webView
//每次在重新定向URL的時候迎罗,這個方法就會被觸發(fā),通常情況片仿,我們會在這里做一些攔截完成js和本地的間接交互什么的
- (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;
//攔截URL看是不是 判斷是否是自定義的路徑
//在html中我們設置了 (WVJBIframe.src = 'kadios://__KAD_BRIDGE_LOADED__';)
//isCorrectProcotocolScheme 方法判斷 scheme 是否等于 kadios
//isBridgeLoadedURL 方法 判斷 host 是否等于 __KAD_BRIDGE_LOADED__
if ([_base isCorrectProcotocolScheme:url]) {
if ([_base isBridgeLoadedURL:url]) {
//oc 調用 js
//此時執(zhí)行在工程里面放置的JS文件
[_base injectJavascriptFile];
//由于js函數(shù)中一進來便主動觸發(fā)了registerHandler纹安,所以url變成了 kadios://__KAD_QUEUE_MESSAGE__
//文件執(zhí)行完畢 然后走下面else if
} else if ([_base isQueueMessageURL:url]) {
//url所以變成了 kadios://__KAD_QUEUE_MESSAGE__
// evaluateJavascript:@"WebViewJavascriptBridge._fetchQueue();"
//_fetchQueue() 取出消息字典里的內容 在js里面把 字典變成字符串
//?? [webView stringByEvaluatingJavaScriptFromString:@"document.location.href"] //獲取當前頁面的url。
//如果不是js調用的 messageQueueString = [];
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;
}
}
[_base injectJavascriptFile]加載的js文件
//js這邊 先把方法名字砂豌、參數(shù)厢岂、處理方法保存成一個字典在轉成json字符串,在通過UIWebview調用js中某個方法把這個json字符串傳到Native中去阳距,同時把這個處理的方法以key-value形式放到一個js的字典中塔粒。
// UIWebView在收到這個json之后,進行數(shù)據處理筐摘、還有js的回掉的處理方法(就是那個callbackId)處理完成后也會拼成一個key-value字典通過調用js傳回去(可以直接調用js)卒茬。
// js在接到這個json后,根據responseId讀取responseCallbacks中處理方法進行處理Native code返回的數(shù)據咖熟。
;(function() {
if (window.WebViewJavascriptBridge) { return }
var messagingIframe
//發(fā)送的消息隊列
var sendMessageQueue = []
//接收消息隊列
var receiveMessageQueue = []
//回調映射
var messageHandlers = {}
//改變 wvjbscheme 和 __WVJB_QUEUE_MESSAGE__ 這連個組合標識 給webview 的 delegate判斷用的
var CUSTOM_PROTOCOL_SCHEME = 'kadios'
var QUEUE_HAS_MESSAGE = '__KAD_QUEUE_MESSAGE__'
//js 調用 OC 如果有回調會加入這里
var responseCallbacks = {}
var uniqueId = 1
//獲取iframe
function _createQueueReadyIframe(doc) {
messagingIframe = doc.createElement('iframe')
messagingIframe.style.display = 'none'
messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE
//在iframe 最加 src(路徑)屬性
doc.documentElement.appendChild(messagingIframe)
}
function init(messageHandler) {
if (WebViewJavascriptBridge._messageHandler) { throw new Error('WebViewJavascriptBridge.init called twice') }
WebViewJavascriptBridge._messageHandler = messageHandler
var receivedMessages = receiveMessageQueue
receiveMessageQueue = null
for (var i=0; i<receivedMessages.length; i++) {
_dispatchMessageFromObjC(receivedMessages[i])
}
}
function send(data, responseCallback) {
_doSend({ data:data }, responseCallback)
}
//oc 調用 js
function registerHandler(handlerName, handler) {
messageHandlers[handlerName] = handler
}
//js調用oc
function callHandler(handlerName, data, responseCallback) {
_doSend({ handlerName:handlerName, data:data }, responseCallback)
}
//js需要觸發(fā)oc必須調用該方法
function _doSend(message, responseCallback) {
if (responseCallback) {
//callbackId 該事件的id 把回調方法與id綁定
var callbackId = 'cb_'+(uniqueId++)+'_'+new Date().getTime()
responseCallbacks[callbackId] = responseCallback
message['callbackId'] = callbackId
}
//把字典放到消息隊列中
sendMessageQueue.push(message)
//產生一個url 執(zhí)行URL重定向
messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE
}
//sendMessageQueue 里面是消息字典 內容有 handlerName data callbackId
//這方法是取出 sendMessageQueue的內容
function _fetchQueue() {
var messageQueueString = JSON.stringify(sendMessageQueue)
//獲取完后吧發(fā)送消息隊列清空 防止重復調用
sendMessageQueue = []
return messageQueueString
}
//js中處理oc的消息 判斷oc獲取到js端的消息
function _dispatchMessageFromObjC(messageJSON) {
setTimeout(function _timeoutDispatchMessageFromObjC() {
var message = JSON.parse(messageJSON)
var messageHandler
var responseCallback
//判斷oc的回調是 response 還是 callback
if (message.responseId) {
responseCallback = responseCallbacks[message.responseId]
if (!responseCallback) { return; }
responseCallback(message.responseData)
delete responseCallbacks[message.responseId]
} else {
//js 調用 oc后 oc返回數(shù)據
if (message.callbackId) {
var callbackResponseId = message.callbackId
responseCallback = function(responseData) {
_doSend({ responseId:callbackResponseId, responseData:responseData })
}
}
//這段代碼 觸發(fā)js調用 responseCallback
var handler = WebViewJavascriptBridge._messageHandler
if (message.handlerName) {
handler = messageHandlers[message.handlerName]
}
try {
handler(message.data, responseCallback)
} catch(exception) {
if (typeof console != 'undefined') {
console.log("WebViewJavascriptBridge: WARNING: javascript handler threw.", message, exception)
}
}
}
})
}
//OC調用js的方法通過這方法
function _handleMessageFromObjC(messageJSON) {
if (receiveMessageQueue) {
receiveMessageQueue.push(messageJSON)
} else {
_dispatchMessageFromObjC(messageJSON)
}
}
//js定義 WebViewJavascriptBridge對象
window.WebViewJavascriptBridge = {
init: init,
send: send,
registerHandler: registerHandler,
callHandler: callHandler,
_fetchQueue: _fetchQueue,
_handleMessageFromObjC: _handleMessageFromObjC
}
var doc = document
_createQueueReadyIframe(doc)
var readyEvent = doc.createEvent('Events')
readyEvent.initEvent('WebViewJavascriptBridgeReady')
readyEvent.bridge = WebViewJavascriptBridge
doc.dispatchEvent(readyEvent)
})();
OC調用JS的原理
OC調用JS方法使用callHandler
方法圃酵,該方法把需要的(data,handlerName馍管,callbackId) 封裝成字典郭赐,然后把字典轉為String,然后把該String寫入到webView中确沸,實現(xiàn)與js交互捌锭。
- (void)sendData:(id)data responseCallback:(WVJBResponseCallback)responseCallback handlerName:(NSString*)handlerName {
//轉換成字典
NSMutableDictionary* message = [NSMutableDictionary dictionary];
if (data) {
message[@"data"] = data;
}
if (responseCallback) {
NSString* callbackId = [NSString stringWithFormat:@"objc_cb_%ld", ++_uniqueId];
self.responseCallbacks[callbackId] = [responseCallback copy];
message[@"callbackId"] = callbackId;
}
if (handlerName) {
message[@"handlerName"] = handlerName;
}
[self _queueMessage:message];
}
//webView 寫入js OC調用JS方法
- (void)_dispatchMessage:(WVJBMessage*)message {
NSString *messageJSON = [self _serializeMessage:message pretty:NO];
[self _log:@"SEND" json:messageJSON];
messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\\" withString:@"\\\\"];
messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\"" withString:@"\\\""];
messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\'" withString:@"\\\'"];
messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\n" withString:@"\\n"];
messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\r" withString:@"\\r"];
messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\f" withString:@"\\f"];
messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\u2028" withString:@"\\u2028"];
messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\u2029" withString:@"\\u2029"];
//調用js中的_handleMessageFromObjC方法
NSString* javascriptCommand = [NSString stringWithFormat:@"WebViewJavascriptBridge._handleMessageFromObjC('%@');", messageJSON];
if ([[NSThread currentThread] isMainThread]) {
[self _evaluateJavascript:javascriptCommand];
} else {
dispatch_sync(dispatch_get_main_queue(), ^{
[self _evaluateJavascript:javascriptCommand];
});
}
}