0
votes

New in iOS 8 is a bug that blocks the UI when more than a couple of JavaScript to native code communications take place.

Native to Javascript communication is via UIWebView's stringByEvaluatingJavaScriptFromString, and JavaScript to native is done via a custom URL Scheme.

If I do a three or more webview -> native communications, the first two happen immediately, however the subsequent ones take 10 seconds per communication to occur.

Anyone experiencing this?

EDIT: 2014/09/23

Specifically, the problem occurs if you do two javascript to native calls consecutively,

[self.webView stringByEvaluatingJavaScriptFromString:@"calliOS('aurlscheme://awebview?say=two'); calliOS('aurlscheme://awebview?say=cat');"];

where calliOS() is a JavaScript function that uses to the iframe hack to call the native code through the URL scheme.

If you replace the iframe hack with a straight call to 'window.location',

[self.webView stringByEvaluatingJavaScriptFromString:@"window.location='aurlscheme://awebview?say=two';window.location='aurlscheme://awebview?say=cat';"];

You lose all but the last URL scheme request.

So in conclusion, when making rapid consecutive javascript to native calls, either you use the iframe hack and the UI freezes for a few seconds, or you use the window.location way and lose all but the last request.

1
I'm also interested in this. Can you get custom URL schemes to work at all? So far, I don't get notified of them in webView:shouldStartLoadWithRequest:navigationType on iOS 8.Alexander
Yes, it works for me, although I use application:application handleOpenURL:url to catch the request, parse the GET query string and the pass the dictionary of parameters to the corresponding view controller.paulvs
@AlexanderCollins, see my answer below, it works for me in iOS 8.paulvs

1 Answers

1
votes

This solution works for me.

Every time JavaScript sends a request to the native code, the delegate method,

- webView:shouldStartLoadWithRequest:navigationType:

must receive the request before the JavaScript sends a new request, otherwise we encounter all sorts of bugs. So, we implement a cache in the JavaScript to hold the pending requests, and only fire a new one once the previous request has been received.

So, the solution is:

JavaScript

// We need an array (cache) to hold the pending request URLs.
var iOSRequestCache = [];

/**
 Add a new iOS request to the cache.
 @params (Dictionary) - The query parameters to send to the native code.
 */
function newiOSRequest(parameters) {
    // Make the full request string.
    var str = [];
    for (var p in parameters)
        if (parameters.hasOwnProperty(p)) {
            str.push(encodeURIComponent(p) + "=" + encodeURIComponent(parameters[p]));
        }
    var queryString = str.join("&");

    var request = 'myurlscheme://webview?' + queryString;

    // Add the new request to the cache.
    iOSRequestCache.push(request);

    console.log("Added new request: " + request);

    // If this new request is the only one in the cache, fire it off to the native side. Else, do nothing.
    if (iOSRequestCache.length == 1) {
        console.log("Fired request: " + request);
        window.location = iOSRequestCache[0];
    }
}

/**
 Called by the native side to notify that a request was processed and to procced with the next pending request, if any.
 */
function resolveiOSRequest(request) {
    console.log("Resolving request: " + request);
    // Search for the processed request in the cache and delete it.
    var requestIndex = iOSRequestCache.indexOf(request);
    if (requestIndex != -1) {
        iOSRequestCache.splice(requestIndex, 1);   // Remove the request from the array.
    }
    console.log("Deleting resolving request: " + request);

    if (iOSRequestCache.length >= 1) {
        console.log("Firing next request: " + request);
        window.location = iOSRequestCache[0];
    }

    console.log("Resolved request: " + request);
}

Objective-C (native code)

/*
 Called whenever a URL is requested inside the web view.
 */
- (BOOL)webView:(UIWebView *)inWeb shouldStartLoadWithRequest:(NSURLRequest *)inRequest navigationType:(UIWebViewNavigationType)inType
{
    // The URL Scheme we use for web view <-> native communication (we grab it from the .plist file of the project target.)
    NSString *URLScheme = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleURLTypes"][0][@"CFBundleURLSchemes"][0];

    // If we get a request from this URL scheme, we grab the parameters and take the appropriate action.
    if ([inRequest.URL.scheme isEqualToString:URLScheme]) {
        // Get the query parameters.
        NSMutableDictionary *params = [NSMutableDictionary dictionary];
        NSArray *pairs = [inRequest.URL.query componentsSeparatedByString:@"&"];
        for (NSString *pair in pairs)
        {
            NSArray *elements = [pair componentsSeparatedByString:@"="];
            NSString *key = elements[0];
            NSString *value = elements[1];
            key = [key stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
            value = [value stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
            [params setObject:value forKey:key];
        }

        // Here we notify the JavaScript that we received this communication so that the next one can be sent.
        NSString *javaScript = [NSString stringWithFormat:@"resolveiOSRequest('%@')", inRequest.URL.absoluteString];
        [self.webView stringByEvaluatingJavaScriptFromString:javaScript];

        // Call the method that handles our JavaScript to native requests.
        // HERE WE TAKE WHATEVER ACTION WE WANT WITH THE DICTIONARY THAT
        // THE JAVASCRIPT SENT US.
        [self handleWebViewMessage:params];
    }

    return YES;

}

Usage:

newiOSRequest({
    animal               : "cat",
    favfood              : "gocat",
});