在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不会刷新页面,重新创建一个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传过来的参数,且可以回调ObjC*/
//OC 调用JS registerHandler
bridge.registerHandler('getUserInfos', function(data, responseCallback) {
document.getElementById("changeTitle").innerHTML = data;
//回调给ObjC
responseCallback({'userId': '123456', 'title': 'Hello World!'})
})
})
app中使用WebViewJavascriptBridge的代码
//创建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也可以传参数过来。data就是JS所传的参数,不一定需要传
// OC端通过responseCallback回调JS端,JS就可以得到所需要的数据
[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的回调数据
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注册事件)中,方便后续处理
_base.delegate = self;
}
webView
//每次在重新定向URL的时候,这个方法就会被触发,通常情况,我们会在这里做一些拦截完成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
//此时执行在工程里面放置的JS文件
[_base injectJavascriptFile];
//由于js函数中一进来便主动触发了registerHandler,所以url变成了 kadios://__KAD_QUEUE_MESSAGE__
//文件执行完毕 然后走下面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这边 先把方法名字、参数、处理方法保存成一个字典在转成json字符串,在通过UIWebview调用js中某个方法把这个json字符串传到Native中去,同时把这个处理的方法以key-value形式放到一个js的字典中。
// UIWebView在收到这个json之后,进行数据处理、还有js的回掉的处理方法(就是那个callbackId)处理完成后也会拼成一个key-value字典通过调用js传回去(可以直接调用js)。
// js在接到这个json后,根据responseId读取responseCallbacks中处理方法进行处理Native code返回的数据。
;(function() {
if (window.WebViewJavascriptBridge) { return }
var messagingIframe
//发送的消息队列
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需要触发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 执行URL重定向
messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE
}
//sendMessageQueue 里面是消息字典 内容有 handlerName data callbackId
//这方法是取出 sendMessageQueue的内容
function _fetchQueue() {
var messageQueueString = JSON.stringify(sendMessageQueue)
//获取完后吧发送消息队列清空 防止重复调用
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返回数据
if (message.callbackId) {
var callbackResponseId = message.callbackId
responseCallback = function(responseData) {
_doSend({ responseId:callbackResponseId, responseData:responseData })
}
}
//这段代码 触发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中,实现与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];
});
}
}