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.
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!




You just have to click on it to get back to test
editor and have all javascript code needed to get a reference to this node,
something like this:

This
is the default status of the selector, it simply says that the current firefox
behavior is "as before", using the same global session for all tabs.

First, we create one session linked to a
personnal account. In this example I take gmail, but it can be any website :
twitter, facebook, flickr, … whatever! To do so we click on the profile
selector and get menu on the left and we click on "+ new profiles" and get the
right’s one.

Then, we do the same for one professional account:
yoono.test.

Later, we can reopen one of these sessions
directly to the homepage with the profile selector and get automatically signed
in. The "switch to" link doesn’t go to the homepage and only reload the current
tab with the selected session (very usefull for Facebook connect, sharing,
…). 

