Swift WKWebView crash on didFailProvisionalNavigation - ios

We are encountering an intermittent (happens on some devices, some of the time) crash that we are having a hard time nailing down, and are unable to reproduce on-demand. This is related to Swift 3 and the WKWebView component, specifically its callback protocol crashes when attempting to get the error code via a switch statement. see below:
func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
if let err = error as? URLError {
switch(err.code) { // Exception occurs on this line
case .cancelled:
Hint(hide: true)
case .cannotFindHost:
Hint(hide: false, hint:.CannotFindHost)
case .notConnectedToInternet:
Hint(hide: false, hint: .NoInternet)
case .resourceUnavailable:
Hint(hide: false)
case .timedOut:
Hint(hide: false)
default:
Hint(hide: false)
print("error code: " + String(describing: err.code) + " does not fall under known failures")
}
}
}
func Hint(hide: Bool, hint:SomeCustomEnum = SomeCustomEnum.Default) {
//Dosomething with ui to let user know something bad happened
}
the error stack indicates:
0 _BridgedStoredNSError.code.getter
1 _BridgedStoredNSError.code.getter
2 specialized WebKitController.webView(WKWebView, didFailProvisionalNavigation : WKNavigation!, withError : Error) -> ()
3 #obj WebKitController.webView(WKWebView, didFailProvisionalNavigation : WKNavigation!, withError : Error) -> ()
...
Reviewing the code it seems as if it should be effectively free from issues since the variable err should be successfully optionally-unwrapped as a valid URLError object by the time the switch statement is invoked. The switch statement at that point should be guaranteed a value in err.code since .code is not optional for URLError.
Attempts to artificially cause an error that might explain the issue have so far not provided much insight. ie. if I create my own custom error without a code property, then attempt to cast that as a URLError, it gracefully falls out of the optional assignment.
Any help or suggestions to revolve or even further troubleshoot is appreciated, in the mean time will continue to try to reproduce on a consistent basis.

When looking into the Swift Bug (https://bugs.swift.org) submission site I was able to find a description of the problem, ie. Error cast to URLError can result in the property .code being missing:
https://bugs.swift.org/browse/SR-3879?jql=text%20~%20%22URLError%22
This has a link to the following reference which seems to be the solution (still in process)
https://bugs.swift.org/browse/SR-3881
Effectively URLError is missing two .code definitions:
NSURLErrorAppTransportSecurityRequiresSecureConnection
NSURLErrorDataLengthExceedsMaximum
So if your encountering a crash when referencing the .code property of a URLError, you can check for it by casting to NSError and checking against the NSError .code property.
We are mitigating it with a temporary workaround until the bug is resolved (below only addresses the NSURLErrorAppTransportSecurityRequiresSecureConnection (int -1022) type failures):
func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
let nserr = error as NSError
if nserr.code == -1022 {
Hint(hide: false, hint: .NSURLErrorAppTransportSecurityRequiresSecureConnection)
} else if let err = error as? URLError {
switch(err.code) { // Exception no longer occurs
case .cancelled:
Hint(hide: true)
case .cannotFindHost:
Hint(hide: false, hint:.CannotFindHost)
case .notConnectedToInternet:
Hint(hide: false, hint: .NoInternet)
case .resourceUnavailable:
Hint(hide: false)
case .timedOut:
Hint(hide: false)
default:
Hint(hide: false)
print("error code: " + String(describing: err.code) + " does not fall under known failures")
}
}
}
func Hint(hide: Bool, hint:SomeCustomEnum = SomeCustomEnum.Default) {
//Dosomething with ui to let user know something bad happened
}

Related

LAContext evaluatePolicy not showing TouchID prompt

func authenticateBiometry(completion: #escaping ErrorHandler) {
context.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: " ") { success, error in
guard let error = error else {
if success {
completion(nil)
}
return
}
completion(error)
}
}
But it prompts for touchId/faceId only the first time. What can I do to ask for it for example every time when I tap button? Let's say every 15 seconds.
Just tested locally and it works for me, this is the only way I found. I saw your comment above but I will put an answer here because probably someone will not find it ugly haha :).
I took some time to google some kind of reset method in LAContext class, but didn't find anything.
The solution was to reset the LAContext at the beginning of the method called on button tap:
func authenticateBiometry(completion: #escaping ErrorHandler) {
context = LAContext() //THIS, implying that context is declared as `var`
context.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: " ") { success, error in
guard let error = error else {
if success {
completion(nil)
}
return
}
completion(error)
}
}
You will be able to prompt face/touch ID on each button click, as soon as one ends.

evaluateJavascript is not executing function

How do i execute javascript function at runtime, the function to load the chat window does not get executed
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
let javascript =
"const params = {typeId: ‘someid’, callback: getContextCallback} loadChatWindow(params)"
evaluateJavascript(javascript, completion:{ _ in })
}
try catching your error in evaluateJavascript completionHandler to see if your javascript string is correct or not (you need semicolon to separate the js statements as mentioned in the comment). also, evaluateJavascript is webView's method so it should be called like this:
webView.evaluateJavaScript(javascript) { (result, error) in
print(error as? String)}

Swift + iOS: How to call closure from within async call?

I have a web view inside of my app. I'm currently looking at each request and seeing if they are a youtube video before allowing / canceling the request:
extension WebBrowserViewController: WKNavigationDelegate {
func webView(webView: WKWebView, decidePolicyForNavigationAction navigationAction: WKNavigationAction, decisionHandler: (WKNavigationActionPolicy) -> Void) {
if (urlVideoType(webView.URL!) != VideoUrl.Unknown && urlVideoType(webView.URL!) != VideoUrl.Youtube) {
...
} else if urlVideoType(webView.URL!) == VideoUrl.Youtube {
// the success and failure part don't work
presentYoutubeVideoIfAvailable(webView.URL!, success: decisionHandler(.Cancel), failure: decisionHandler(.Allow)) <-------------------
} else {
decisionHandler(.Allow)
}
}
}
My presentYoutubeVideoIfAvailable method uses this youtube url parser pod: https://github.com/movielala/YoutubeSourceParserKit
However, that makes an async call and I don't know how to make it call the success and failure methods in my method:
func presentYoutubeVideoIfAvailable(url: NSURL, success: (), failure: ()) {
Youtube.h264videosWithYoutubeURL(url) { [unowned self] (videoInfo, _) -> Void in
switch videoInfo?["url"] as? String {
case .Some(let videoUrlString):
VideoStore.addVideo(url.absoluteString, title: videoInfo?["title"] as? String ?? "Unknown")
success
self.presentVideo(NSURL(string: videoUrlString)!)
case .None:
print("herereee") // gets printed, so I know it's here
failure // doesn't do anything. it should be allowing the request but it isn't <-----------------------------------
self.showError(.YoutubeParsingFail)
}
}
}
I want it so that if the youtube pod returns a url, it should cancel the web view request, and if it fails I want it to continue with the request. How would I do this?
The reason nothing is happening here is that you're not calling the closures. Closures should be called like any other function in swift, like so:
success()
failure()

how to Add timeout for WKWebview

How to write a timeout handler for WKWebView, when default delegates are not getting called for didFailNavigation.
WKWebView delegate are set & DidFinishNavigation or didFailProvisionalNavigation is getting called.
Use the error.code value of the error that didFailProvisionalNavigation creates and add your 'handler' code there:
func webView(webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: NSError) {
if error.code == -1001 { // TIMED OUT:
// CODE to handle TIMEOUT
} else if error.code == -1003 { // SERVER CANNOT BE FOUND
// CODE to handle SERVER not found
} else if error.code == -1100 { // URL NOT FOUND ON SERVER
// CODE to handle URL not found
}
}
Use this delegate method
webView:didFailProvisionalNavigation:withError:
Document
Invoked when an error occurs while starting to load data for the main frame.
And check the error code
NSURLErrorTimedOut = -1001
All the error code list
One possible solution is to add custom timer, which starts as you call loadHTML, loadRequest methods and times out on custom interval
Compared to Timer , asyncAfter(deadline:) is more light-weighted.
var isTimeOut = true
DispatchQueue.main.asyncAfter(deadline: .now() + timeOut) {
if isTimeOut{
// do time out thing
}
}
check isTimeOut according to WKNavigationDelegate
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!){
isTimeOut = false
}

How can I get failed URL from didFailProvisionalNavigation method

I'm trying to display error message for my web view, and I need to know url which is not available, so I implemented delegate method:
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation withError:(NSError *)error {
[self.addressBar finishLoadingProgressAnimated:YES];
NSLog(#"%#", webView.URL);
[self showErrorPageForURL:error.userInfo[NSErrorFailingURLStringKey]];
}
but NSErrorFailingURLStringKey is deprecated, so how can I get failed URL?
WKNavigation's interface is empty. webView.URL == nil at that moment.
Swift 3 or Swift 4
func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
if error._domain == "WebKitErrorDomain" {
if let info = error._userInfo as? [String: Any] {
if let url = info["NSErrorFailingURLKey"] as? URL {
}
if let urlString = info["NSErrorFailingURLStringKey"] as? String {
}
}
}
}
You can use NSURLErrorFailingURLStringErrorKey to replace NSErrorFailingURLStringKey. If you jump to its definition in Xcode, you will find the below discussion.
This constant supersedes NSErrorFailingURLStringKey, which was
deprecated in Mac OS X 10.6. Both constants refer to the same value
for backward-compatibility, but this symbol name has a better prefix.

Resources