Let’s change the subject: this time no more talks about memory but always on UIWebView component. When we use this component for something else than just displaying webpages, like building UI with HTML, Javascript, … We often want to call Javascript functions from objective C and the opposite.
Call Javascript function from Objective-C:
The first move is easily done with the following piece of code:
// In your Javascript files: function myJavascriptFunction () { // Do whatever your want! } // ----------------------------------- // And in your Objective-C code: // Call Javascript function from Objective-C: [webview stringByEvaluatingJavaScriptFromString:@"myJavascriptFunction()"];
Call Objective-C function from Javascript:
But calling objective-c from a Javascript function is not easy as Iphone SDK doesn’t offer any native way to do this! So we have to use any king of hack to do this …
The most known, used and buggy practice is to register a UIWebViewDelegate on your web view and « catch-and-immediatly-cancel » a location change done in javascript.
(a very extremely plenty much advised practice!)
// In Objective-C - someFunctionOnInit { webView = [[UIWebView alloc] init]; // Register the UIWebViewDelegate in order to shouldStartLoadWithRequest to be called (next function) webView.delegate = self; } // This function is called on all location change : - (BOOL)webView:(UIWebView *)webView2 shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType { // Intercept custom location change, URL begins with "js-call:" if ([[[request URL] absoluteString] hasPrefix:@"js-call:"]) { // Extract the selector name from the URL NSArray *components = [requestString componentsSeparatedByString:@":"]; NSString *function = [components objectAtIndex:1]; // Call the given selector [self performSelector:NSSelectorFromString(functionName)]; // Cancel the location change return NO; } // Accept this location change return YES; } - (void)myObjectiveCFunction { // Do whatever you want! } // ----------------------------------- // Now in your javascript simply do this to call your objective-c function: // /!\ But for those who just read title and code, take care, this is a buggy practice /!\\n window.location = "js-call:myObjectiveCFunction";
What’s wrong with UIWebViewDelegate, shouldStartLoadWithRequest and location change ?
There is weird but apprehensible bugs with this practice:
a lot of javascript/html stuff get broken when we cancel a location change:
- All setInterval and setTimeout immediatly stop on location change
- Every innerHTML won’t work after a canceled location change!
- You may get other really weird bugs, really hard to diagnose …
Sample application highlighting these bugs
Key files of this example:
- MyWebview.m: Objective-c part, that inherit from UIWebView. Set the UIWebViewDelegate and catch requests in shouldStartLoadWithRequest selector.
- NativeBridge.js: Tiny javascript library in order to change the location and offer a way to send arguments and receive a response.
- webview-script.js: Test case script, that highlight these bugs.
In webview-script.js: InnerHTML stop working whereas textContent continues to …
document.getElementById("count").innerHTML = i; document.getElementById("count2").textContent = i;
But we can’t charge Apple on this bug. I mean we try to load another location in the document we are working on! The webview component may start doing stuff before the delegate call, which cancel the load …
We have to find alternative way to communicate with the native code!
Better way to call Objective-C
The only thing we have to change is in Javascript code. Instead of changing the document location, we create an IFrame and set its location to a value that trigger the shouldStartLoadWithRequest method.
And voilà!
var iframe = document.createElement("IFRAME"); iframe.setAttribute("src", "js-frame:myObjectiveCFunction"; document.documentElement.appendChild(iframe); iframe.parentNode.removeChild(iframe); iframe = null;
Here is another sample application, with exactly the same structures and test file.
But this time you are going to see innerHTML and setTimeout working! Again, this demo contains a library (NativeBridge.js) that allow to send arguments to native code and get back a result in javascript asynchronously, with a callback function.
Best practice example!
Free Objective-C<->Javascript library
Finally I provide the communication library under LGPL licence so it can ease your work on iphone platform! As I know that it’s really not easy ;-)
- MyWebView.m: ObjectiveC part,
- NativeBridge.js: Javascript side.
The code is full of comment, so you may easily use and tweak it!
Github repo