Question

Note this is not a duplicate due to the "in iOS 7" part of my question and the fact previous behavior (and thus answers to previous questions) has changed.

I want to know how JavaScript code invoked via a UIWebView can invoke Objective-C code in iOS 7. This is something I have previously used extensively in projects in the past, however the behavior on iOS 7 has changed with the result that the webView delegate's didFailLoadWithError gets called when previously it did not.

I used to invoke Objective-C functionality using the custom url trick:

function invokeObjectiveC(action, target, data) {
   var iframe = document.createElement("IFRAME");
   iframe.setAttribute("src", "MY-URLScheme:" + action + ":" + target + ":" + data);
   document.documentElement.appendChild(iframe);
   iframe.parentNode.removeChild(iframe);
   iframe = null;
}

The particular line that causes didFailLoadWithError: to get called is this one:

 document.documentElement.appendChild(iframe);

The other common alternative approach sprinked around the internet to do the same thing is this:

window.location = "MY-URLScheme:" + action + ":" + target + ":" + data;

And this works on iOS 7 in that didFailLoadWithError does not get called, however it has a (known) problem that if its called twice quickly in succession the first call is lost.

So I wondered how Cordova is doing this, so I downloaded source code and had a look and I think they are doing it similar to me like this (the 3rd line is different) :

var iframe = document.createElement("IFRAME");
iframe.setAttribute("src", "MY-URLScheme:" + action + ":" + target + ":" + data);
document.body.appendChild(iframe);
iframe.parentNode.removeChild(iframe);
iframe = null;

However that still results in didFailLoadWithError getting called. Which has left me puzzled as to why Cordova hasn't done anything to rectify this (assuming I'm looking at their code correctly).

The fact that didFailLoadWithError gets called does not seem to be detremental as pages still load and the Objective-C still gets invoked. However it is very very unsatisfactory to have code that results in an error method getting called so I want to find an alternative way.

So the question is: is there a way of invoking Objective-C from within the JS loaded in a UIWebView reliably and without didFailLoadWithError getting called on iOS 7?

Can anybody confirm Cordova is using the way above or something else?

Solutions using JavaScriptCore within a UIWebView (as opposed to external to the UIWebView) look undocumented/unsupported in iOS/fragile? For example:

Access the JavaScriptCore engine of a UIWebView

Trigger objective C method from javascript using JavaScriptCore in iOS 7 in ViewControllers

Was it helpful?

Solution

The iframe workaround is a bit buggy and is not very reliable. JavaScriptCore is a good approach but the framework is private on iOS 6 and iOS 5. There is a better and a faster way to invoke Objetive-C from a UIWebView. You can implement a category method for the alert function.

@implementation UIWebView (JavaScriptAlert)
- (void)webView:(UIWebView *)sender runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WebFrame *)frame 
{
    if ([message hasPrefix:CUSTOM_SCHEME_STRING]) {
        //process your message
    }
    else {
        UIAlertView * alert = [[UIAlertView alloc] initWithTitle:@"Alert" message:message delegate:nil cancelButtonTitle:@"Ok" otherButtonTitles: nil];
        [alert show];
        [alert release];
    }
} 
@end

When you call the window.alert() method from javascript the above Objetive-C function is called. Use a custom prefix for your messages and fallback to a UIAlertView when you want a standard alert.

This method works on iOS 5, 6 and 7 with no problems. It's very reliable. I've tested invoking alert 10000 times in a row and no messages get lost. That's not the case with the iframe solution, many messages get lost if you create iframes too quickly, so you have implement a batch queue on your JS code and sync them with a ~1ms interval.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top