10

I have a fairly large HTML/JS/CSS application that works great when running as a web application with Safari on the iPhone.

When running this same application in an UIWebView within a native iPhone application calls within jQuery to create HTML fragments fail silently (ie: $("<div>HELLO WORLD</div>"); will not create the element.

I've tracked this down to the following equivalent code snippet in clean jQuery method:

var div = document.createElement(“div”); div.innerHTML = “<div>HELLO WORLD</div>”;

When I look at div.outerHTML I see <div>/<div>

div.innerHTML returns an empty string.

This does not appear to be a jQuery problem, nor does this happen 100% of the time. I haven’t been able to find a pattern, but in some cases it works 2-3 times in a row, sometimes if fails 5-6 times consecutively. This seems to only shows up when running the application inside a UIWebView in an Objective-C application. Also I’ve only seen this on an actual device running iOS 4.2, not the emulator.

Has anyone run into anything similar? Does anyone have a fix?

Kevin
  • 9,309
  • 12
  • 44
  • 51

3 Answers3

5

I had this problems too. It happens when the CPU of the phone is very busy (say 100%). Then the rendering engine sometimes forget about innerHTML settings.

The solution included in my unify project is to test if there is an element in childNodes, otherwise apply it again.

var target = document.createElement("div");
var text = "<div>Hello World</div>";
target.innerHTML = text;
var self = this;
self.__intervalHandle = window.setInterval(function() {
  target.innerHTML = text:
  if (target.firstChild) {
    window.clearInterval(self.__intervalHandle);
    self.__intervalHandle = null;
  }
}, 100);

This forces the rendering engine to apply the innerHTML to the DOM and gives the rendering engine some time (100 ms in this example, a good value in our tests) to handle it.

  • Thanks Sebastian - this will probably get me down the right path. This call is actually in jQuery so an async approach might not work but it's good to know what's going on, and I can probably develop some sort of work-around with that in mind. – Kevin Jan 05 '11 at 16:25
2

Well, the solution [NOT a production quality solution] posted by Sebastian worked, but I couldn’t confirm if CPU load would cause this issue. I generated a lot of background load on the iOS host and couldn’t reproduce this issue.

Upon further investigation, the rendering issue seems to be a side effect of iOS shell canceling the navigation. Once the navigation is canceled by the iOS shell, the rendering engine probably take that as not needing to render more UI [basically doesn’t render anything for a small period].

One way to fix this would be to send commands to iOS shell as hash (#) parameters instead of a URL. This way iOS shell will get the commands and doesn’t need to cancel the navigation. This approach seems to work in the test code below. So, if window.location is set to location1, it alerts “At: 1” and element e2 has no value. And if the window.location is set to location2, it alerts “At: 0” and element e2 has the value.

@Kevin, could you confirm that you were canceling the navigation on iOS host when this behavior happened.

Test Code:

Javascript:

    var location1 = "myApp://Test";
    var location2 = "#myApp://Test";
    $("#change").live("click", function (e) {
        var element = document.getElementById("e1");
        window.location = location1; var i = 0;            
        element.innerHTML = "At: " + i;
        window.__intervalHandle = window.setInterval(function () {
            var html = element.innerHTML;
            if (html) {
                 alert(html);
                window.clearInterval(window.__intervalHandle);
                window.__intervalHandle = null;
            } else {
                element.innerHTML = "At: " + ++i;
            }
        }, 1);
        document.getElementById("e2").innerHTML = "Test";
    });

iOS pseudo code:

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
NSURL* u = [ request URL];

if( [[u scheme] compare:@"myapp" ] == NSOrderedSame) {
{ 
return NO; // don’t navigate
}
return YES; // navigate
}
C. Ross
  • 31,137
  • 42
  • 147
  • 238
user634645
  • 21
  • 1
  • Thanks C. Ross - I wan't able to directly tie the failing behavior to anything I was doing, but will change all my code to what you suggest when I get back to the application and follow up on this thread. For now I've found that if I create the element without any children and then add children after the element is created and attached to the DOM it seems to work fine. This took a while but it seemd to resolve the issue. – Kevin Apr 05 '11 at 13:26
2

You should take a look at http://blog.techno-barje.fr/post/2010/10/06/UIWebView-secrets-part3-How-to-properly-call-ObjectiveC-from-Javascript.

It turns out that cancelling navigation as part of the -webView:shouldStartLoadWithRequest: delegate method may cause issues with innerHTML. I updated our JS code to use the method recommended in that article and I haven't seen the innerHTML issue crop up in a while.