Introduction to the
This article introduces you to an excellent open source small project: WebViewJavascriptBridge.
It gracefully implements interoperability between JS and ios ObjC Nativecode when using UIWebView, supports message sending, receiving, message handler registration and invocation, and callback Settings for message processing.
Like the name of the project, it’s a bridge that connects UIWebView to Javascript. After joining the project, the interaction between them became friendly.
When interacting with JS in UIWebView in Native code, it looks like this:
[_bridge send:@"A string sent from ObjC before Webview has loaded." responseCallback:^(id error, id responseData) { if (error) { NSLog(@"Uh oh - I got an error: %@", error); } NSLog(@"objc got response! %@ %@", error, responseData); }];
Js in UIWebView also becomes very simple when interacting with native code. For example, when invoking the processor, you can define the callback processing logic:
CallHandler ('testObjcCallback', {'foo': 'bar'}, function(response) { log('Got response from testObjcCallback', response) })
Take a look at its implementation, which contains three files altogether:
WebViewJavascriptBridge.h WebViewJavascriptBridge.m WebViewJavascriptBridge.js.txt
They interact in the following pattern:
Is obvious: WebViewJavascriptBridge. Js. TXT is mainly used for connecting the UIWebView web page, and WebViewJavascriptBridge. H/m is mainly used for dealing with ObjC native code. As a whole, they actually play the role of a “bridge”. These three files encapsulate their specific interaction processing methods and only open some external apis related to business processing. Therefore, when you need UIWebView to interact with Native code, you need not consider too many interaction problems by introducing this library. The entire Bridge is transparent to you, and you feel as though the front-end and back-end of Web programming are as clear when programming.
Here’s a quick list of what it can do:
For the need of expression, I call the UI side related to UIWebView, and the processing code of objC side is called the Native side.
[1] UI end
(1) UI side supports setting the default processor of the message during initialization (the message here refers to the message received from the Native end)
(2) Send messages from THE UI end to the Native end, and support the definition of callback processing after the response of the Native end
(3) UI end calls the processor defined by Native, and supports the callback processing definition after the response of Native end
(4) The UI side registers the processor (called by the Native side), and supports the definition of the response processing logic for the Native side
[2] Native end
(1) The Native terminal supports setting the default processor of messages during initialization (messages here refer to messages sent from the UI terminal)
(2) Send messages from the Native end to the UI end, and support the definition of callback processing logic after the RESPONSE of the UI end
(3) The Native end calls the processor defined by the UI end, and supports the definition of the callback processing logic at the Native end after the RESPONSE is given by the UI end
(4) The Native end registers the processor (for the UI end to call), and supports the definition of the response processing logic for the UI end
The UI end and the Native end are completely equal ends, and the implementation is also equal. One segment is the sender of the message, and the other is the receiver. For the sake of confusion, the definition of “response” and “callback” I use here in this context is explained:
(1) Response: the receiving end responds to the sending end
(2) Callback: the processing logic invoked at the receiving end after the sender receives the reply from the receiving end
Let’s analyze the source code:
WebViewJavascriptBridge. Js. TXT:
The main work is as follows:
(1) to create an iFrame used to send message (by creating a hidden ifrmae, and set the URL to send a request, triggering the UIWebView shouldStartLoadWithRequest callback protocol)
(2) create a core object WebViewJavascriptBridge, and define several methods to it, most of these methods are public API methods
(3) to create an event: WebViewJavascriptBridgeReady, and dispatch (trigger) it.
Code reading
The UI end implementation
For (1), the corresponding code is as follows:
/* * Create an iFrame, */ function _createQueueReadyIframe(doc) {messagingIframe = doc.createElement('iframe') messagingIframe.style.display = 'none' doc.documentElement.appendChild(messagingIframe) }
For the WebViewJavascriptBridge in (2), its object has the following methods:
window.WebViewJavascriptBridge = { init: init, send: send, registerHandler: registerHandler, callHandler: callHandler, _fetchQueue: _fetchQueue, _handleMessageFromObjC: _handleMessageFromObjC }
Method implementation:
/* * The default message handler is used when processing a message from objC if the message has no handler set, */ function init(messageHandler) {if (WebViewJavascriptBridge._messageHandler) {throw new Error('WebViewJavascriptBridge.init called twice') } WebViewJavascriptBridge._messageHandler = messageHandler var ReceivedMessages = receiveMessageQueue receiveMessageQueue = null // If there are messages in the receiving queue, process for (var I =0; i
There are two internal methods involved:
*/ function _doSend(message, ResponseCallback (responseCallback) {var callbackId = 'js_cb_'+(uniqueId++) // generates a unique identifier for the callback object and stores it in a collection object ResponseCallbacks [callbackId] = responseCallback // add a key-value pair - 'callbackId':callbackId message['callbackId'] = callbackId } sendMessageQueue.push(JSON.stringify(message)) messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE} /* * Internal methods: handle messages from objC */ function _dispatchMessageFromObjC(messageJSON) {setTimeout(function _timeoutDispatchMessageFromObjC() { var message = JSON.parse(messageJSON) var messageHandler if (message.responseId) { Var responseCallback = responseCallbacks[message.responseid] responseCallback(message.error, message.responseData) delete responseCallbacks[message.responseId] } else { var response if (message.callbackId) { var callbackResponseId = message.callbackId response = { respondWith: function(responseData) { _doSend({ responseId:callbackResponseId, responseData:responseData }) }, respondWithError: function(error) { _doSend({ responseId:callbackResponseId, Error :error})}} var handler = WebViewJavascriptBridge._messageHandler // If the message contains a message handler, the message handler is used; If (message.handlerName) {handler = messageHandlers[message.handlerName]} try {handler(message.data, response) } catch(exception) { console.log("WebViewJavascriptBridge: WARNING: javascript handler threw.", message, exception) } } }) }There are also two JS methods that are called directly by the native side (which themselves serve the native side) :
/* * get the queue, */ function _fetchQueue() {var messageQueueString = SendMessageQueue. Join (MESSAGE_SEPARATOR) sendMessageQueue = [] return messageQueueString} /* * Handle messages from ObjC */ Function _handleMessageFromObjC(messageJSON) {// If the receiving queue object exists, the message is queued. If (receiveMessageQueue) {receiveMessageQueue. Push (messageJSON)} else {_dispatchMessageFromObjC(messageJSON)} }The final piece of code defines an event and fires, setting the WebViewJavascriptBridge object defined above as an attribute of the event:
Var readyEvent = doc.createEvent('Events') var doc = document _createQueueReadyIframe(doc) ReadyEvent. InitEvent (' WebViewJavascriptBridgeReady) readyEvent. Bridge = WebViewJavascriptBridge / / trigger event doc.dispatchEvent(readyEvent)Native client implementation
It's basically the same as the above, but the syntax is different:
WebViewJavascriptBridge.h/.m
It can actually be viewed as a Controller of UIWebView, implementing the UIWebViewDelegate protocol:
@interface WebViewJavascriptBridge : NSObject + (id)bridgeForWebView:(UIWebView*)webView handler:(WVJBHandler)handler; + (id)bridgeForWebView:(UIWebView*)webView webViewDelegate:(id )webViewDelegate handler:(WVJBHandler)handler; + (void)enableLogging; - (void)send:(id)message; - (void)send:(id)message responseCallback:(WVJBResponseCallback)responseCallback; - (void)registerHandler:(NSString*)handlerName handler:(WVJBHandler)handler; - (void)callHandler:(NSString*)handlerName; - (void)callHandler:(NSString*)handlerName data:(id)data; - (void)callHandler:(NSString*)handlerName data:(id)data responseCallback:(WVJBResponseCallback)responseCallback; @endThe implementation of the method is actually similar, but we're just going to look at one protocol method on UIWebView
ShouldStartLoadWithRequest:
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType { if (webView ! = _webView) { return YES; } NSURL *url = [request URL]; If ([[url scheme] isEqualToString:CUSTOM_PROTOCOL_SCHEME]) {// If ([[url host] IsEqualToString :QUEUE_HAS_MESSAGE]) {// Flush data from the queue [self _flushMessageQueue]; } else { NSLog(@"WebViewJavascriptBridge: WARNING: Received unknown WebViewJavascriptBridge command %@://%@", CUSTOM_PROTOCOL_SCHEME, [url path]); } return NO; } else if (self.webViewDelegate) { return [self.webViewDelegate webView:webView shouldStartLoadWithRequest:request navigationType:navigationType]; } else { return YES; }}Use the sample
The UI end
/ / to WebViewJavascriptBridgeReady event to register a Listener document. The addEventListener (' WebViewJavascriptBridgeReady onBridgeReady, Function onBridgeReady(event) {var bridge = event. 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 + (data ? ': ' + JSON.stringify(data) : InsertBefore (el, log.children[0])} else {log.appendChild(el)}} Init (function(message) {log('JS got a message', message)}) // Register a handler named testJavascriptHandler Bridge. RegisterHandler ('testJavascriptHandler', function(data, response) { log('JS handler testJavascriptHandler was called', data) response.respondWith({ 'Javascript Says':'Right back atcha! '})}) // Create a button to send a message to native document.getElementById('buttons').appendChild(document.createElement('button')) button.innerHTML = 'Send message to ObjC' button.onTouchStart = function(e) {e.preventDefault() // bridge.send('Hello from JS button')} Document. The body. The appendChild (document. The createElement method (" br ")) / / create a used to invoke the native button var callbackButton = end processor document.getElementById('buttons').appendChild(document.createElement('button')) callbackButton.innerHTML = 'Fire testObjcCallback' callbackButton.ontouchstart = function(e) { e.preventDefault() log("Calling handler testObjcCallback") CallHandler ('testObjcCallback', {'foo': 'bar'}, function(response) { log('Got response from testObjcCallback', response) }) } }Native client
/ / instantiate a webview and join into the window UIWebView * webview = [[UIWebView alloc] initWithFrame: self. Window. The bounds]; [self.window addSubview:webView]; // enableLogging [WebViewJavascriptBridge enableLogging]; // instantiate WebViewJavascriptBridge and define the default message handler on the native side. _bridge = [WebViewJavascriptBridge bridgeForWebView:webView Handler :^(id data, WVJBResponse *response) { NSLog(@"ObjC received message from JS: %@", data); UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"ObjC got message from Javascript:" message:data delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil]; [alert show]; }]; // Register a handler called testObjcCallback for uI-side calls, [_bridge registerHandler:@"testObjcCallback" handler:^(id data, WVJBResponse *response) { NSLog(@"testObjcCallback called: %@", data); [response respondWith:@"Response from testObjcCallback"]; }]; [_bridge send:@"A string sent from ObjC before Webview has loaded." responseCallback:^(id error, id responseData) { if (error) { NSLog(@"Uh oh - I got an error: %@", error); } NSLog(@"objc got response! %@ %@", error, responseData); }]; // Call a handler named testJavascriptHandler defined on the UI side, [_bridge callHandler:@"testJavascriptHandler" data:[NSDictionary dictionaryWithObject:@"before Ready" forKey:@"foo"]]; [self renderButtons:webView]; [self loadExamplePage:webView]; [_bridge send:@"A string sent from ObjC after Webview has loaded."];Project operation screenshot: