UIWebView secrets - part3 - How to properly call ObjectiveC from Javascript

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:

Sample application highlighting these bugs

Key files of this example: 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 ;-)
The code is full of comment, so you may easily use and tweak it!

Github repo

Comments

You can use your Fediverse (i.e. Mastodon, among many others) account to reply to this post
(Note that comments from locked accounts won't be visible on the blog, but only to me)