Skip to content

Commit

Permalink
Restructure of WebViewJavascriptBridge, now the bridges delegate does…
Browse files Browse the repository at this point in the history
…n't lose the delegation of the webview, it gets, well, bridged. Modified naming to be more conventional
  • Loading branch information
sergiocampama committed Dec 4, 2011
1 parent 4dbb54e commit 83448f4
Show file tree
Hide file tree
Showing 4 changed files with 182 additions and 125 deletions.
28 changes: 16 additions & 12 deletions WebViewJavascriptBridge/Classes/WebViewJavascriptBridge.h
@@ -1,29 +1,33 @@
#import <Foundation/Foundation.h> #import <UIKit/UIKit.h>


@protocol WebViewJavascriptBridgeDelegate <NSObject> @class WebViewJavascriptBridge;


- (void) handleMessage:(NSString*) message fromWebView: (UIWebView *)theWebView; @protocol WebViewJavascriptBridgeDelegate <UIWebViewDelegate>

- (void)javascriptBridge:(WebViewJavascriptBridge *)bridge receivedMessage:(NSString *)message fromWebView:(UIWebView *)webView;


@end @end


@interface WebViewJavascriptBridge : NSObject <UIWebViewDelegate> { @interface WebViewJavascriptBridge : NSObject <UIWebViewDelegate>
id <WebViewJavascriptBridgeDelegate> _delegate;
NSMutableArray *_startupMessageQueue;
}


/** Delegate to receive messages from javascript. */ /** Delegate to receive messages from javascript. */
@property (readwrite, assign) id <WebViewJavascriptBridgeDelegate> delegate; /** Defined as IBOutlet for Interface Builder assignment */
@property (nonatomic, assign) IBOutlet id <WebViewJavascriptBridgeDelegate> delegate;

/** Init with a predefined delegate */
- (id)initWithDelegate:(id <WebViewJavascriptBridgeDelegate>)delegate;


/** Creates & returns new autoreleased javascript Bridge with no delegate set. */ /** Convenience methods for obtaining a bridge */
+ (id) javascriptBridge; + (id)javascriptBridge;
+ (id)javascriptBridgeWithDelegate:(id <WebViewJavascriptBridgeDelegate>)delegate;


/** Sends message to given webView. You need to integrate javascript bridge into /** Sends message to given webView. You need to integrate javascript bridge into
* this view before by calling WebViewJavascriptBridge#webViewDidFinishLoad: with that view. * this view before by calling WebViewJavascriptBridge#webViewDidFinishLoad: with that view.
* *
* You can call this method before calling webViewDidFinishLoad: , than all messages * You can call this method before calling webViewDidFinishLoad: , then all messages
* will be accumulated in _startupMessageQueue & sended to webView, provided by first * will be accumulated in _startupMessageQueue & sended to webView, provided by first
* webViewDidFinishLoad: call. * webViewDidFinishLoad: call.
*/ */
- (void) sendMessage:(NSString*) message toWebView:(UIWebView *) theWebView; - (void)sendMessage:(NSString *)message toWebView:(UIWebView *)webView;


@end @end
233 changes: 137 additions & 96 deletions WebViewJavascriptBridge/Classes/WebViewJavascriptBridge.m
Expand Up @@ -2,10 +2,10 @@


@interface WebViewJavascriptBridge () @interface WebViewJavascriptBridge ()


@property (readwrite,retain) NSMutableArray* startupMessageQueue; @property (nonatomic,strong) NSMutableArray *startupMessageQueue;


- (void) _flushMessageQueueFromWebView: (UIWebView *) theWebView; - (void)_flushMessageQueueFromWebView:(UIWebView *)webView;
- (void) _doSendMessage:(NSString*)message toWebView:(UIWebView *) theWebView; - (void)_doSendMessage:(NSString*)message toWebView:(UIWebView *)webView;


@end @end


Expand All @@ -14,135 +14,176 @@ @implementation WebViewJavascriptBridge
@synthesize delegate = _delegate; @synthesize delegate = _delegate;
@synthesize startupMessageQueue = _startupMessageQueue; @synthesize startupMessageQueue = _startupMessageQueue;


static NSString* MESSAGE_SEPERATOR = @"__wvjb_sep__"; static NSString *MESSAGE_SEPARATOR = @"__wvjb_sep__";
static NSString* CUSTOM_PROTOCOL_SCHEME = @"webviewjavascriptbridge"; static NSString *CUSTOM_PROTOCOL_SCHEME = @"webviewjavascriptbridge";
static NSString* QUEUE_HAS_MESSAGE = @"queuehasmessage"; static NSString *QUEUE_HAS_MESSAGE = @"queuehasmessage";


+ (id) javascriptBridge - (id)initWithDelegate:(id <WebViewJavascriptBridgeDelegate>)delegate
{ {
return [[[self alloc] init] autorelease]; if ((self = [super init]) )
}

- (id) init
{
if ( (self = [super init]) )
{ {
self.startupMessageQueue = [[NSMutableArray new] autorelease]; self.delegate = delegate;
self.startupMessageQueue = [[[NSMutableArray alloc] init] autorelease];
} }


return self; return self;
} }


- (void) dealloc - (id)init
{ {
self.delegate = nil; return [[[WebViewJavascriptBridge alloc] initWithDelegate:nil] autorelease];
self.startupMessageQueue = nil; }

+ (id)javascriptBridgeWithDelegate:(id <WebViewJavascriptBridgeDelegate>)delegate
{
return [[[self alloc] initWithDelegate:delegate] autorelease];
}

+ (id)javascriptBridge
{
return [[[self alloc] init] autorelease];
}

- (void)dealloc
{
_delegate = nil;
[_startupMessageQueue release];


[super dealloc]; [super dealloc];
} }


- (void)sendMessage:(NSString *)message toWebView: (UIWebView *) theWebView { - (void)sendMessage:(NSString *)message toWebView:(UIWebView *)webView {
if (self.startupMessageQueue) { [self.startupMessageQueue addObject:message]; } if (self.startupMessageQueue) { [self.startupMessageQueue addObject:message]; }
else { [self _doSendMessage:message toWebView: theWebView]; } else { [self _doSendMessage:message toWebView: webView]; }
} }


- (void)_doSendMessage:(NSString *)message toWebView: (UIWebView *) aWebView { - (void)_doSendMessage:(NSString *)message toWebView:(UIWebView *)webView {
message = [message stringByReplacingOccurrencesOfString:@"\\n" withString:@"\\\\n"]; message = [message stringByReplacingOccurrencesOfString:@"\\n" withString:@"\\\\n"];
message = [message stringByReplacingOccurrencesOfString:@"'" withString:@"\\'"]; message = [message stringByReplacingOccurrencesOfString:@"'" withString:@"\\'"];
message = [message stringByReplacingOccurrencesOfString:@"\"" withString:@"\\\""]; message = [message stringByReplacingOccurrencesOfString:@"\"" withString:@"\\\""];
[aWebView stringByEvaluatingJavaScriptFromString:[NSString stringWithFormat:@"WebViewJavascriptBridge._handleMessageFromObjC('%@');", message]]; [webView stringByEvaluatingJavaScriptFromString:[NSString stringWithFormat:@"WebViewJavascriptBridge._handleMessageFromObjC('%@');", message]];
}

- (void)_flushMessageQueueFromWebView:(UIWebView *)webView {
NSString *messageQueueString = [webView stringByEvaluatingJavaScriptFromString:@"WebViewJavascriptBridge._fetchQueue();"];
NSArray* messages = [messageQueueString componentsSeparatedByString:MESSAGE_SEPARATOR];
for (id message in messages) {
[self.delegate javascriptBridge:self receivedMessage:message fromWebView:webView];
}
} }


- (void)webViewDidFinishLoad:(UIWebView *)theWebView { #pragma mark UIWebViewDelegate
NSString* js;
js = [NSString stringWithFormat:@";(function() {" - (void)webViewDidFinishLoad:(UIWebView *)webView {
"if (window.WebViewJavascriptBridge) { return; };" NSString *js = [NSString stringWithFormat:@";(function() {"
"var _readyMessageIframe," "if (window.WebViewJavascriptBridge) { return; };"
" _sendMessageQueue = []," "var _readyMessageIframe,"
" _receiveMessageQueue = []," " _sendMessageQueue = [],"
" _MESSAGE_SEPERATOR = '%@'," " _receiveMessageQueue = [],"
" _CUSTOM_PROTOCOL_SCHEME = '%@'," " _MESSAGE_SEPERATOR = '%@',"
" _QUEUE_HAS_MESSAGE = '%@';" " _CUSTOM_PROTOCOL_SCHEME = '%@',"
"" " _QUEUE_HAS_MESSAGE = '%@';"
"function _createQueueReadyIframe(doc) {" ""
" _readyMessageIframe = doc.createElement('iframe');" "function _createQueueReadyIframe(doc) {"
" _readyMessageIframe.style.display = 'none';" " _readyMessageIframe = doc.createElement('iframe');"
" doc.documentElement.appendChild(_readyMessageIframe);" " _readyMessageIframe.style.display = 'none';"
"}" " doc.documentElement.appendChild(_readyMessageIframe);"
"" "}"
"function _sendMessage(message) {" ""
" _sendMessageQueue.push(message);" "function _sendMessage(message) {"
" _readyMessageIframe.src = _CUSTOM_PROTOCOL_SCHEME + '://' + _QUEUE_HAS_MESSAGE;" " _sendMessageQueue.push(message);"
"};" " _readyMessageIframe.src = _CUSTOM_PROTOCOL_SCHEME + '://' + _QUEUE_HAS_MESSAGE;"
"" "};"
"function _fetchQueue() {" ""
" var messageQueueString = _sendMessageQueue.join(_MESSAGE_SEPERATOR);" "function _fetchQueue() {"
" _sendMessageQueue = [];" " var messageQueueString = _sendMessageQueue.join(_MESSAGE_SEPERATOR);"
" return messageQueueString;" " _sendMessageQueue = [];"
"};" " return messageQueueString;"
"" "};"
"function _setMessageHandler(messageHandler) {" ""
" if (WebViewJavascriptBridge._messageHandler) { return alert('WebViewJavascriptBridge.setMessageHandler called twice'); }" "function _setMessageHandler(messageHandler) {"
" WebViewJavascriptBridge._messageHandler = messageHandler;" " if (WebViewJavascriptBridge._messageHandler) { return alert('WebViewJavascriptBridge.setMessageHandler called twice'); }"
" var receivedMessages = _receiveMessageQueue;" " WebViewJavascriptBridge._messageHandler = messageHandler;"
" _receiveMessageQueue = null;" " var receivedMessages = _receiveMessageQueue;"
" for (var i=0; i<receivedMessages.length; i++) {" " _receiveMessageQueue = null;"
" messageHandler(receivedMessages[i]);" " for (var i=0; i<receivedMessages.length; i++) {"
" }" " messageHandler(receivedMessages[i]);"
"};" " }"
"" "};"
"function _handleMessageFromObjC(message) {" ""
" if (_receiveMessageQueue) { _receiveMessageQueue.push(message); }" "function _handleMessageFromObjC(message) {"
" else { WebViewJavascriptBridge._messageHandler(message); }" " if (_receiveMessageQueue) { _receiveMessageQueue.push(message); }"
"};" " else { WebViewJavascriptBridge._messageHandler(message); }"
"" "};"
"window.WebViewJavascriptBridge = {" ""
" setMessageHandler: _setMessageHandler," "window.WebViewJavascriptBridge = {"
" sendMessage: _sendMessage," " setMessageHandler: _setMessageHandler,"
" _fetchQueue: _fetchQueue," " sendMessage: _sendMessage,"
" _handleMessageFromObjC: _handleMessageFromObjC" " _fetchQueue: _fetchQueue,"
"};" " _handleMessageFromObjC: _handleMessageFromObjC"
"" "};"
"setTimeout(function() {" ""
" var doc = document;" "setTimeout(function() {"
" _createQueueReadyIframe(doc);" " var doc = document;"
" var readyEvent = doc.createEvent('Events');" " _createQueueReadyIframe(doc);"
" readyEvent.initEvent('WebViewJavascriptBridgeReady');" " var readyEvent = doc.createEvent('Events');"
" doc.dispatchEvent(readyEvent);" " readyEvent.initEvent('WebViewJavascriptBridgeReady');"
"}, 0);" " doc.dispatchEvent(readyEvent);"
"})();", "}, 0);"
MESSAGE_SEPERATOR, "})();",
CUSTOM_PROTOCOL_SCHEME, MESSAGE_SEPARATOR,
QUEUE_HAS_MESSAGE]; CUSTOM_PROTOCOL_SCHEME,
QUEUE_HAS_MESSAGE];


[theWebView stringByEvaluatingJavaScriptFromString:js]; [webView stringByEvaluatingJavaScriptFromString:js];


for (id message in self.startupMessageQueue) { for (id message in self.startupMessageQueue) {
[self _doSendMessage:message toWebView: theWebView]; [self _doSendMessage:message toWebView: webView];
} }

self.startupMessageQueue = nil; self.startupMessageQueue = nil;

if(self.delegate != nil && [self.delegate respondsToSelector:@selector(webViewDidFinishLoad:)])
{
[self.delegate webViewDidFinishLoad:webView];
}
} }


- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
{
if(self.delegate != nil && [self.delegate respondsToSelector:@selector(webView:didFailLoadWithError:)])
{
[self.delegate webView:webView didFailLoadWithError:error];
}
}


- (BOOL)webView:(UIWebView *)theWebView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType { - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
NSURL *url = [request URL]; NSURL *url = [request URL];
if (![[url scheme] isEqualToString:CUSTOM_PROTOCOL_SCHEME]) { return YES; } if (![[url scheme] isEqualToString:CUSTOM_PROTOCOL_SCHEME])

{
if(self.delegate != nil && [self.delegate respondsToSelector:@selector(webView:shouldStartLoadWithRequest:navigationType:)])
{
[self.delegate webView:webView shouldStartLoadWithRequest:request navigationType:navigationType];
}
return YES;
}

if ([[url host] isEqualToString:QUEUE_HAS_MESSAGE]) { if ([[url host] isEqualToString:QUEUE_HAS_MESSAGE]) {
[self _flushMessageQueueFromWebView: theWebView]; [self _flushMessageQueueFromWebView: webView];
} else { }
NSLog(@"WARNING: Received unknown WebViewJavascriptBridge command %@://%@", CUSTOM_PROTOCOL_SCHEME, [url path]); else {
NSLog(@"WebViewJavascriptBridge: WARNING: Received unknown WebViewJavascriptBridge command %@://%@", CUSTOM_PROTOCOL_SCHEME, [url path]);
} }


return NO; return NO;
} }


- (void) _flushMessageQueueFromWebView: (UIWebView *) theWebView { - (void)webViewDidStartLoad:(UIWebView *)webView
NSString* messageQueueString = [theWebView stringByEvaluatingJavaScriptFromString:@"WebViewJavascriptBridge._fetchQueue();"]; {
NSArray* messages = [messageQueueString componentsSeparatedByString:MESSAGE_SEPERATOR]; if(self.delegate != nil && [self.delegate respondsToSelector:@selector(webViewDidStartLoad:)])
for (id message in messages) { {
[self.delegate handleMessage:message fromWebView: theWebView]; [self.delegate webViewDidStartLoad:webView];
} }
} }


@end @end
2 changes: 1 addition & 1 deletion WebViewJavascriptBridge/ExampleAppDelegate.h
Expand Up @@ -7,6 +7,6 @@
@property (strong, nonatomic) UIWebView *webView; @property (strong, nonatomic) UIWebView *webView;
@property (strong, nonatomic) WebViewJavascriptBridge *javascriptBridge; @property (strong, nonatomic) WebViewJavascriptBridge *javascriptBridge;


- (void) loadExamplePage; - (void)loadExamplePage;


@end @end

0 comments on commit 83448f4

Please sign in to comment.