How do I properly implement didFailProvisionalNavigation with WKWebView? - ios

I'm making a web browser and like every other web browser I'd like to inform the user of the error that occurred when a web page failed to load.
Currently, I'm displaying the localizedDescription of the error in the didFailProvisionalNavigation method. That's all!
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation withError:(NSError *)error
{
[self presentError:error.localizedDescription animated:YES];
[_delegate webViewDidFailNavigation:self error:error];
}
The presentError method handles some custom UI stuff irrelevant to the question.
The method above works excellent for displaying the usual errors such as:
The internet connection appears to be offline.
A server with the specified hostname could not be found.
But it also displays some unusual errors such as:
The operation could not be completed(NSURL ErrorDomain error -999)
I don't like this method for two reasons:
The error message displays some technical stuff "NSURL" but I could ignore that.
Most importantly most of the time when the error above is being displayed the web page is perfectly usable but since it's a didFailProvisionalNavigation error I have to disable the page from being used and instead present the error.
I'm not aware of what other minor errors could occur that shouldn't be handled the way I'm handling them. Unfortunately, I don't know of a way to distinguish between major errors such as "Connection appears to be offline" and minor errors such as "Operation couldn't be completed".
What is the standard way of handling navigation failures?

I needed to use didFailProvisionalNavigation too. For now I solved it like this:
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(null_unspecified WKNavigation *)navigation withError:(nonnull NSError *)error
{
if(error.code != -999) // Prevent the error from showing when didFailProvisionalNavigation is triggered after a cancelled load (reload)
{
// Show the error
}
}

Instead of
webView(_:didFailProvisionalNavigation:withError:)
why do you not use this?
webView(_:didFail:withError:)
It seems that should only be called when there is an error preventing the site from displaying content, not when a page is already rendered and trying to load the next page.

Related

What's the difference between `WKNavigationDelegate` `didFail` and `didFailProvisionalNavigation`

iOS WKWebView's WKNavigationDelegate has two methods to handle a failed navigation:
webView(_:didFail:withError:): "Tells the delegate that an error occurred during navigation."
webView(_:didFailProvisionalNavigation:withError:): "Tells the delegate that an error occurred during the early navigation process."
The docs only tell us that the one type occurs earlier in the navigation process than the other. The error arguments are generic, so no help there. Brave and Firefox iOS only handle didFailProvisionalNavigation as far as I can tell from reading their source.
My questions are:
What's the difference exactly between the two types of errors?
Is there a list of errors that can occur for each?
When is it necessary to handle didFail seeing that browsers don't seem to handle that?
webView(_:didFailProvisionalNavigation:withError:)
This method handles errors that happen before the resource of the url can even be reached. These errors are mostly related to connectivity, the formatting of the url, or if using urls which are not supported.
The error codes delivered here are found in
https://developer.apple.com/documentation/cfnetwork/cfnetworkerrors
Typical examples are
kCFURLErrorTimedOut = -1001 // timed out
kCFURLErrorUnsupportedURL = -1002 // unsupported URL
kCFURLErrorCannotFindHost = -1003 // host can not be found
kCFURLErrorFileDoesNotExist = -1100 // file does not exist on the server
webView(_:didFail:withError:)
Here, errors are reported that happen while loading the resource. These are usually errors caused by the content of the page, like invalid code in the page itself that the parser can't handle.

When does the navigationDelegate in a WKWebView gets called if an error occurs?

Assume you are browsing the New York Times home page and you click a link to read an article before the page fully loads. The navigationDelegate get's called:
- (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error;
The error is:
"The operation couldn’t be completed."
As we normally expect the new page (that contains the article) should start loading after the error is thrown. In fact, I have logged this and I observed that the article page starts loading before the error is thrown, at exactly the time you click the article's title.
What I'm trying to do is to get the article URL and the solution I came up with is to store the last URL that the webView starts loading and when the navigationDelegate get's called to report the error use the lastURL as the new page URL.
When this method get's called:
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler;
Store the URL here:
#property(nonatomic, retain) NSURL *lastURL;
What I'm afraid of is if the navigationDelegate get's called after a few more sub frames of the new web page are loaded, that will mess up the lastURL entirely. It will not have stored the article's true URL but some other URL that is part of the article.
So, when exactly does the navigationDelegate get's called to report the error? Can someone reassure me that my fears won't come true?

UIWebView - what NSError's are thrown?

I'm trying to throw up an alert to users when a UIWebView fails to load a page because it can't reach the server. I'm using the delegate method:
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error {
// show NSAlert telling user there was a problem
}
(docs: https://developer.apple.com/library/ios/documentation/uikit/reference/UIWebViewDelegate_Protocol/Reference/Reference.html#//apple_ref/occ/intfm/UIWebViewDelegate/webView:didFailLoadWithError:)
The problem is that this method is called for other things as well - such as when you visit another page before the previous one has finished loading, etc. What specific NSError's should I check for for throwing my NSAlert? What NSError's does UIWebView throw? I can't see this documented anywhere!
Thanks.
If anyone does come up across the same problem, the solution ended up been to:
NSLog the error code and description every time the error function was called.
Do some stuff in my app that would generate errors
Watch the output to establish which codes corresponded to which actions (the error descriptions helped).
This is achieved simply by:
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error {
NSLog(#"%d", error.code);
NSLog(error.localizedDescription);
}

Connection Error Occurs When Browsing Too Fast

In the app that I'm writing, I check to see if the device has an internet connection. I put a connection error image over the screen, and hide it unless the device is not connected. There is an odd issue though. I implemented a simple back button for the UIWebView, but when I press it too fast, the connection error occurs. Here is the code I use to check for connection, and decide whether to display the error:
-(void)webView:(UIWebView *)myWebView didFailLoadWithError:(NSError *)error {
_connectionError.hidden = NO;
}
So, I think the only way to solve this issue would be to have it check if there is a connection one time, only when the app first launches, and never run again for the remainder of the time. I'm extremely new to Objective-C, and have no idea how to do this. I'm thinking that I should put something in viewDidLoad, or implement some way to have the method run only once, but I have no idea how to do that.
Here's the code for the back button:
- (IBAction)backButtonTapped:(id)sender {
[_viewWeb goBack];
}
Call the method stopLoading on the webView before the goBack method to make sure there is no multiple request going which can cause the connection error:
- (IBAction)backButtonTapped:(id)sender {
[_viewWeb stopLoading];
[_viewWeb goBack];
}
To check for a connection you can use Reachability in your project. You can then use this answer to see how to use it. This would be more efficient and cleaner than using a UIWebview.

How to catch WebKit Errors

I wonder how i can catch and handle WebKit Errors in my iPad App. I found no information about ErrorDomain and ErrorCodes up till now. Is this a miracle?
This is what i see from time to time at the console in Xcode during testing on device (iPad2, iOS 5.0.1).
WebKit discarded an uncaught exception in the webView:runJavaScriptAlertPanelWithMessage:initiatedByFrame: delegate: <(null)> A route has already been registered for class 'Publication' and HTTP method 'ANY'
How can i catch WebKit Errors? This Error raises during an alert(); in JS within the HTML Page that's loaded in a UIWebView.
I am not sure but maybe the:
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
could put some light on that issue (that's UIWebViewDelegate). Set the delegate and implement this method. Print the error description to the console and check it.
Here is a Swift 3 version of RaffAl's answer.
func webView(webView: UIWebView, didFailLoadWithError error: NSError) {
print("An error occurred!: \(error)")
}

Resources