Our iOS app has a Web View that is rendering pages from file: URLs. When the user touches an <A> element to switch pages, about 20% of the time, it gets stuck as follows:
User taps <A> link
We get WebView.shouldStartLoadWithRequest callback
We get WebView.webViewDidStartLoad callback
nothing happens after this
The screen still shows the original page with the link on it. We can break the logjam in two ways:
Rotate the device
Tap the screen
At that point, the page will immediately finish loading.
We used the recipe from here:
Javascript console.log() in an iOS UIWebView
to allow us some insight into the page load. We put the javascript-side stuff right in the first script file we load on the page, and it doesn't print its message until after we do the rotate-or-tap workaround.
So it appears that it is getting stuck somewhere between starting to load the page, and starting to evaluate the stuff on the page.
We have tried a number of work-around, none of which helped:
Setting location.href instead of using the tag
Setting location.href from javascript timeout
In the didStartLoad callback, created a thread that called setNeedDisplay on the webView over and over
Any idea what we might be doing wrong?
You could use gdb within XCode to debug the problem. I think canidates for messages that could have break points are:
- (void)webViewDidStartLoad:(UIWebView *)webView
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
I'd also add a break in whatever UIViewController is showing your UIWebView for:
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
Then you could step through and hopefully catch what's wrong with your UIWebView subclass.
If you don't want to add breakpoints you could just run your app in the XCode debugger and hit the "pause execution" button above the console window when your app becomes unresponsive while loading.
It's hard to know what's going on without any code to go off of but I bet Xcode can help you find the issue quickly.
Good luck!
Found the solution for this weird behavior, at least in my case.
In my App i've created a subclass of UIWebView. I know that Apples documentation notes that you should not subclass UIWebView. But it's "necessary" in my App.
The problem was, that i've overwritten scrollViewDidEndDragging:willDecelerate: without calling super.
- (void) scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
// my code
}
becomes to
- (void) scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
[super scrollViewDidEndDragging:scrollView willDecelerate:decelerate];
// my code
}
Now it always loads the web site when tapping on a html link.
Related
I'm seeing some crashes in my App crash tracking tool. Basically I have a tabBarController, one of the tabs has an embedded UIWebView, another tab has a controller with a UITableView. So what happens is that when user goes to the WebView first, and it starts loading, after a few seconds they go to the tableView controller, the crash randomly happens.
Judged by the timing I saw in crash log, it seems that the webView is about to finish loading when the user taps the UITableView tab, so I inserted a code into the didFinishLoading. This way I can reliably reproduce the crash. The code looks like this:
The webViewController:
- (void)webViewDidFinishLoad:(UIWebView *)webView {
[tabBarController gotoTableViewController];
}
The tableViewController:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self.tableView reloadData];
}
Here is the crash:
bool _WebTryThreadLock(bool), 0x1700113d0: Multiple locks on web thread not allowed! Please file a bug. Crashing now...
I don't know what I can do about it, it seems both the webView and tableView are locking the mainThread? Is there a way to "unload" the webView loading from main thread? I've tried stopLoading, not working...
Found it. It was indeed caused by the file parsing on mainThread. My file parsing is utilising NSRunLoop to parse a XML file. However the file is meant to be used as a cached values and needed to be parsed synchronously, so I put it on mainThread. I tried to put it onto a background thread and use dispatch_sync, the crash's still there. So I tried dispatch_async with a dispatch_semaphore_t, the crash was gone!
I had the same issue, and I also had an NSRunLoop that could potentially ran while the WebView was fetching the data.
Removing the NSRunLoop the problem is now fixed, but I don't understand why the problem happens in first place.
#KKRocks could you elaborate a bit why the NSRunLoop should cause the WebView to run in background?
In iOS, the default behavior when I open my webpage is that after login it opens the next page in a new window. But now when I open it in iOS webview, it does not open the next page. I google about it, and the cause seems to be the popup blocker. So, is there any simple way to disable the popup blocker in the iOS webview?
The webpage might have a redirection that you are not handling properly.
You may first want to use webView:didFailNavigation:withError: or webView:didFailProvisionalNavigation:withError: to get some reasons or clues. And you can try to debug webView:decidePolicyForNavigationAction:decisionHandler: to see whether you are allowing the navigation.
If the redirection somehow generates an about:blank page in the transition, you could try to use UIWebViewDelegate's webView:shouldStartLoadWithRequest:navigationType: and returns a YES if request.URL.absoluteString is indeed about:blank, for example:
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
if ([request.URL.absoluteString isEqualToString:#"about:blank"]) {
return YES;
}
return NO;
}
BTW, you also can try to use Safari to debug webViews in your simulator. For instance, try document.location in your inspector to see what actually is the URL when the page doesn't open.
I noticed a weird bug in my web view. I have an app that checks to see if the device is connected to the internet, if it is connected then the error image remains hidden. If it is not connected, I unhide the error image. The thing is, if I try and click on another link before the page is finished loading, didFailLoadWithError: fires and unhides the connection issue image. Here is the code I'm using:
- (void)webViewDidFinishLoad:(UIWebView *)webView {
NSLog(#"didFinish: %#; stillLoading:%#", [[webView request]URL],
(webView.loading?#"NO":#"YES"));
}
-(void)webView:(UIWebView *)myWebView didFailLoadWithError:(NSError *)error {
NSLog(#"No internet connection");
_connectionError.hidden = NO;
}
If I wait until I see "still loading" in the log, then press another link, all is well. If I press a link before I see that message, then didFailLoadWithError:fires and unhides the connection issue. I'm extremely new to Objective-C, and programming in general, so I have no idea how to fix this.
Is there any simple that I can have didFailLoadWithError: only run once when the app starts up?
The didFailLoadWithError is called everytime the web view failed to load, which happens when you try to load another page before the current page is finished loading.
You should try other methods to detect if there is no internet connection, such as using Reachability: http://developer.apple.com/library/ios/#samplecode/Reachability/Introduction/Intro.html
Another way is simply calling stopLoading in webView:shouldStartLoadWithRequest:navigationType: delegate method for your particular case of triggering didFailLoadWithError when user taps on another link while the previous link is still loading in the web view.
The solution that I used was to check for the error code. If the code equaled the connection error, then I unhid the error image.
Everything works fine, but sometimes uiwebview did not finish the load. I guess that sometimes uiwebview waits to long for response and than do not finish load. Had someone have familiar issue?
Yes, I have implemented the next methods:
- (void)webViewDidStartLoad:(UIWebView *)webView
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
- (void)webViewDidFinishLoad:(UIWebView *)webView
Yes, I setup the delegate. As I said - everything works fine, but sometimes it do not finish to load, it do not receives any error.
- (void)myTimer:(NSTimer *)timer
{
NSLog(#"WV=%# delegate=%# isLoading=%d", self.webView, self.webView.delegate, self.webView.isLoading);
}
It prints all the time:
[378:c07] WV=UIWebView: 0x755f690; frame = (0 0; 320 416); autoresize
= W+H; layer = CALayer: 0x755f6f0>> delegate=MainViewController: 0x755b130> isLoading=1;
I also tried to setup cache policy and disable it. It didn't help.
Can it be the problem with html frame? When it is loading some elements from the webpage are disappear (it happens when webpage is loaded, but usually after elements disappeared it shows a new page, but in this case it is not).
Did you implement didFailLoadWithError:? Implement all the delegate methods, and log various ones, and see what happens - if it just stalls or something happens before it appears to stall.
EDIT: So use a schedule NSTimer that fires every few seconds, and log the following:
the webview object
the delegate
the webView loading property
Here ist the code:
- (void)myTimer:(NSTimer *)timer
{
NSLog(#"WV=%# delegate=%# isLoading=%d", self.webView, self.webView.delegate, self.webView.isLoading);
}
Let it run for at least 10 minutes (go out and get coffee), then report back here. If it prints out all the info for a long time (like 10 min), then if you can post the URL here that would be a big help, so others can try to duplicate the problem.
I've read that reusing UIWebViews is kind of a bad practice. Some code I inherited at work is pushing a variety of content to a UIWebView. Powerpoints, Word documents, and video. This all works just fine under normal circumstances. When we get to switching out the content in the UIWebView too fast, it dumps.
My webView is set as a property. It is hooked up in IB just fine. Normal selection from our tableView loads local content just fine. It takes rapid fire selection of either the same cell or combinations of multiple to get it to crash.
I can capture some error messages for it for the webViewDidFailWithError. But those will trigger even without a crash.
Here is the error localized string.
The operation couldn’t be completed. (NSURLErrorDomain error -999.)
When the app does finally crash, it blows up on this goofy WebCore error.
If anyone has any links or some code examples how to handle this I would appreciate it. Maybe an example how to best reuse my webView property without blowing things up.
I would load some of my code, but there is a lot going on not related to the webView itself. All content being pushed to the webView is done via [self.webView loadRequest:request]; with the request being an NSURLRequest filled with the path to the local content.
I will be very appreciative if anyone can help me out on this one. Fingers crossed for something simple.
I'm not certain this way is the "best" way to solve the issue, but it does seem to be working quite well. Short, sweet, and it works.
I disabled userInteraction with the tableView that updates the content in the webView. Since you have to mash on it so much, missing a tap here or there probably won't be missed. So for now, this is the fix.
#pragma mark -
#pragma mark UIWebViewDelegate methods
-(void)webViewDidStartLoad:(UIWebView *)webView {
[self.tableView setUserInteractionEnabled:NO];
}
-(void)webViewDidFinishLoad:(UIWebView *)webView {
[self.tableView setUserInteractionEnabled:YES];
}
// I re-enable on load failures as they can block you out entirely on fail
-(void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error {
[self.tableView setUserInteractionEnabled:YES];
}
If it’s crashing only when you tap quickly, could you put a gesture recognizer over it to act as a poor man's rate limiter?
Are you using [webview stopLoading]; before loading another request? What you might need to do is cancel or stop the current loading before trying to load a different one. The other option being restrict the user input.
In your UIWebViewDelegate, you could implement webView:shouldStartLoadWithRequest:navigationType: to return NO if there is already a request loading.
I can't help but think you'd be better off not re-using the UIWebView. Ditch the nib, create it programmatically and set it to nil and re-create/re-assign/re-alloc it when the data source changes.
On a different note I would make sure to use NSOperationQueue for the data loading.