views:

1854

answers:

1

I have been working on an iPhone App for a couple of weeks now and one of the earlier features I added was a UIWebView that loaded context sensitive Wikipedia page topics. This was pretty trivial to implement and has been working fine for some time.

Today, I found that functionality had unexpectedly stopped working. I had been fiddling around at the perimeter of that piece of code and initially assumed that I had broken something.

I checked all the obvious places, my urls, was the UIWebView still hooked up in the XIB etc. I didn't find any issues.

Investigating further, I stuck some error handling on my UIWebViewDelegate didFailLoadWithError and found I was getting a -999 error:

NSURLErrorCancelled

Returned when an asynchronous load is canceled.

A Web Kit framework delegate will receive this error when it performs a cancel operation on a loading resource. Note that an NSURLConnection or NSURLDownload delegate will not receive this error if the download is canceled.

So this sounds like making a new request (or cancelling) before the original one has finished. I check my code for anything like this and come up empty.

So I went into my usual downward spiral of paranoia and assumed that Wikipedia was blocking requests based on UAgent or something and went on something of wild goose chase attempting to spoof my way back to a happy place. These attempts were not successful and eventually sanity prevailed. I created a simple python script to mimic the HTTP request I was making from my APP in the simulator, to see what Wikipedia was sending back:

string = "GET /wiki/Franklin_D._Roosevelt HTTP/1.1\r\nHost: en.wikipedia.org\r\nUser-Agent: test\r\nReferer: http://en.wikipedia.org/wiki/Franklin_D._Roosevelt\r\nAccept: */*\r\nAccept-Language: en-us\r\n_Accept-Encoding: gzip, deflate\r\nConnection: keep-alive\r\n\r\n"
import socket
s = socket.socket()
s.connect(("en.wikipedia.org",80))
s.send(string)
x = s.recv (1000)
while (x):
 print x
 x = s.recv (1000)

So I run this guy and discover that Wikipedia is very kindly returning my data promptly and completely. So what is going on?

Chinks started to appear in my every present, paranoid armour "It's always my fault" and I decide to check out if other iPhone apps can view these URLs. I post a tweet with an ironically amusing (I am easily amused) URL and check out if Tweetie can view the URL. It can't.

A friend tries it out in Twitterific. Same problem. Works fine in Safari and the Wikipedia App, but it seems like iPhone apps using a bog standard UIWebView are having problems with Wikipedia pages.

Just to be completely certain there are no other variables, I created a simple test app with just a UIWebView that loads up http://en.wikipedia.com, it fails with the same error (added that code at the end).

So what do you guys think? Is this just a UIWebView bug? Any Apple gronks out there know what is going on here?

Have I missed something completely obvious and I am once again sitting on the train to work without my pants on?

Looking forward to hearing what you think. Keep in mind, this was working fine yesterday.

Debrief (or maybe Post Mortem is more appropriate):

Thanks Duncan for the solution and a pretty clear description of what is going on here.

Looks like the reason I originally saw the error, when I wasn't implementing the didFailLoadWithError delegate method at all, was that the default behavior for that method is apparently to clear the UIWebView and consequently kill the request. When I added my implementation to find out what was going on, I stuck in some code to write the error to the view and, as Duncan points out, this is what got me.

Seems like a pretty horrible solution to ignore -999 error codes in the callback, but I am fine with duct tape.

I tried quite a few apps to test if this was a UIWebView problem (Tweetie, Twitteriffic etc ...) and they all had the issue. It looks like this might be a pretty common oversight for developers. Maybe Apple can clean this up in the next version.

Other interesting point is that when I switched my URLs to use http://en.m.wikipedia.com instead of http://en.wikipedia.com, the problem went away.

// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad {
    [super viewDidLoad];
    _webView = (UIWebView*)self.view;
    _webView.delegate = self;

    // load the url

    // FAIL
    //NSURL * newUrl = [[[NSURL alloc] initWithString:@"http://en.wikipedia.com"] autorelease];

    // OK
    NSURL * newUrl = [[[NSURL alloc] initWithString:@"http://www.stackoverflow.com"] autorelease];

    NSURLRequest * newUrlRequest = [NSURLRequest requestWithURL:newUrl];
    [_webView loadRequest:newUrlRequest];                                    
}

// delegate stuff
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)req navigationType:(UIWebViewNavigationType)navigationType { return YES; }
- (void)webViewDidStartLoad:(UIWebView *)wv
    {
    // starting the load, show the activity indicator in the status bar
    [UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
}

- (void)webViewDidFinishLoad:(UIWebView *)webView
{
    // finished loading, hide the activity indicator in the status bar
    [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
}

- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
{
    NSURLErrorDomain
    // load error, hide the activity indicator in the status bar
    [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
    [_webView loadHTMLString:[[[NSString alloc] initWithFormat:@"Failed to load page %@", [error localizedDescription]] autorelease] baseURL:nil];
}
+6  A: 

Looks like there's an answer here:

http://stackoverflow.com/questions/1024748/how-do-i-fix-nsurlerrordomain-error-999-in-iphone-3-0-os

which in turn refers to:

http://www.iphonedevsdk.com/forum/iphone-sdk-development/6280-uiwebview-didfailloadwitherror-how-get-errors-code-list.html

Looks like you need a hack like this in the webView:didFailLoadWithError: delegate:

if ([error code] != NSURLErrorCancelled) {
    //show error alert, etc.
}

In essence what's happening is the delegate is getting a "cancelled" (-999) failure, that might be originated in javascript or perhaps even in a UIWebView bug.

With your code the webview doesn't show anything at all because you're using the very same UIWebView to display the error. If you had just NSLog'd the error you would have seen a failure, but then the page would have loaded just fine, giving you a hint that the failure is bogus.

duncanwilcox