WKWebView not opening custom URL scheme (js opens custom scheme link in new window)
Asked Answered
S

6

9

I have a WKWebView in my application. I don't use UIWebView, because for some strange reason it doesn't open properly a web page with a lot of JS code in it.

When I tap on the link with custom url scheme "scm://", it does nothing...

My code:

- (void)viewDidLoad {
    // ...

    WKWebViewConfiguration *configuration = [WKWebViewConfiguration new];
    if ([configuration respondsToSelector:@selector(setDataDetectorTypes:)])
        [configuration setDataDetectorTypes:WKDataDetectorTypeLink];

    myWebView = [[WKWebView alloc] initWithFrame:webFrame configuration:configuration];
    myWebView.navigationDelegate = self;

    [self.view addSubview:myWebView];
}

#pragma mark - WKNavigationDelegate

- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
{
    NSURL *requestURL = navigationAction.request.URL;
    UIApplication *app = [UIApplication sharedApplication];
    if ([requestURL.scheme.lowercaseString isEqualToString:@"scm"] && [app canOpenURL:requestURL]) {
        [app openURL:requestURL];
        decisionHandler(WKNavigationActionPolicyCancel);
    }
    else
        decisionHandler(WKNavigationActionPolicyAllow);
}

- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation
{
    [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
}

- (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error
{
    [self handleError:error];
}

- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation withError:(NSError *)error
{
    [self handleError:error];
}

#pragma mark - Handle web view errors.

- (void)handleError:(NSError *)error
{
    UIApplication *app = [UIApplication sharedApplication];
    app.networkActivityIndicatorVisible = NO;

    NSURL *failedUrl = error.userInfo[NSURLErrorFailingURLErrorKey];

    if ([failedUrl.scheme.lowercaseString isEqualToString:@"scm"]) {
        [app openURL:failedUrl];
    }
}

When I click custom url, handleError() is never called, neither decidePolicyForNavigationAction().

Softy answered 31/10, 2017 at 16:46 Comment(0)
S
12

Ok, figured this out... happens that the link was opening in new window, so adding next code along with setting UIDelegate made it work

    // Somewhere in viewDidLoad()...
    myWebView.UIDelegate = self;
}

- (WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures
{
    NSLog(@"createWebViewWithConfiguration %@ %@", navigationAction, windowFeatures);
    if (!navigationAction.targetFrame.isMainFrame) {
        [UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
        [(WKWebView *)_webView loadRequest:navigationAction.request];
    }
    return nil;
}
Softy answered 1/11, 2017 at 18:3 Comment(2)
Tried setting UIDelegate without luck seem like you did something wrong at previous time ;)Nore
Yeah, I didn't add the method createWebViewWithConfiguration, because unfortunately the tutorial Complete Guide to Implementing WKWebView didn't have it.Softy
E
10

The following works in Swift 5.1 on iOS 13.1.3 (variation of @hstdt's answer) for WKWebView handling minimally the following (tested) schemas: sms:, tel:, and mailto:.

Add the following to whereever you're setting up your WKWebView.

// assign the delegate
webView.navigationDelegate = self

Then, add the following function somewhere in your class.

func webView(_ webView: WKWebView,
             decidePolicyFor navigationAction: WKNavigationAction,
             decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {

    // if the request is a non-http(s) schema, then have the UIApplication handle
    // opening the request
    if let url = navigationAction.request.url,
        !url.absoluteString.hasPrefix("http://"),
        !url.absoluteString.hasPrefix("https://"),
        UIApplication.shared.canOpenURL(url) {

        // have UIApplication handle the url (sms:, tel:, mailto:, ...)
        UIApplication.shared.open(url, options: [:], completionHandler: nil)

        // cancel the request (handled by UIApplication)
        decisionHandler(.cancel)
    }
    else {
        // allow the request
        decisionHandler(.allow)
    }
}

Explanation:

  • WKWebView seems to be unequipped to handle non-http(s) url schemas.
  • The above code catches the request before WKWebView has a chance to render/load it to check for a different schema.
  • We check for a non-http(s) schema and have the UIApplication handle it instead.

Note: Comment if you find other schemas working or not working using this solution.

Again, this solution was sourced and modified from @hstdt's answer.

Electrodialysis answered 4/11, 2019 at 1:22 Comment(2)
Not working for intent:// - error: "This app is not allowed to query for scheme intent"Edan
@MdImranChoudhury What app/service uses the intent:// uri?Electrodialysis
C
8
-(void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler{

    NSURLRequest *request = navigationAction.request;
    if(![request.URL.absoluteString hasPrefix:@"http://"] && ![request.URL.absoluteString hasPrefix:@"https://"]) {
        if([[UIApplication sharedApplication] canOpenURL:request.URL]) {
            //urlscheme, tel, mailto, etc.
            [[UIApplication sharedApplication] openURL:request.URL];
            decisionHandler(WKNavigationActionPolicyCancel);
            return;
        }
    }
    decisionHandler(WKNavigationActionPolicyAllow);
}

Note: This answer is just focus on urlscheme, but maybe lead to other problems. Thanks for your feedback!

Consciencestricken answered 10/1, 2018 at 7:0 Comment(0)
F
5

Solution for Swift 5: The following is a variation of mattblessed's answer for WKWebView which works for all application URL-Schemes not only the system URL-Schmes like "sms:", "tel:", and "mailto:"

This solution will work without adding custom URL Schemes to your apps plist file via LSApplicationQueriesSchemes. (Which is necessary since iOS 10 - for detail see this article: canOpenURL not working in ios 10)

Add the following implementation of WKNavigationDelegate to your class:

func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
    if let requestUrl = navigationAction.request.url, requestUrl.isCustomUrlScheme() {
        decisionHandler(.cancel)

        // try to open URLs like e.g. "whatsapp://"
        UIApplication.shared.open(requestUrl, options: [:]) { success in
            if !success {
                //TODO 
                // add your code for handling URLs that can't be opened here - 
                // maybe show an error alert
            }
        }
    } else {
        // allow the request
        decisionHandler(.allow)
    }
}

Add the following Extension of URL to your class:

extension URL {

    func isCustomUrlScheme() -> Bool {
        let webUrlPrefixes = ["http://", "https://", "about:"]

        let urlStringLowerCase = self.absoluteString.lowercased()
        for webUrlPrefix in webUrlPrefixes {
            if urlStringLowerCase.hasPrefix(webUrlPrefix) {
            return false
        }

        return urlStringLowerCase.contains(":")
    }
}
Faceharden answered 28/11, 2019 at 8:45 Comment(0)
N
2

I think this is everything you want about custom scheme ;)

https://medium.com/glose-team/custom-scheme-handling-and-wkwebview-in-ios-11-72bc5113e344

You can use setURLSchemeHandler:forURLScheme: to adds an URL scheme handler object for a given URL scheme.

If you want to add custom scheme by Swift, learn more http://samwize.com/2016/06/08/complete-guide-to-implementing-wkwebview/

Nore answered 31/10, 2017 at 17:14 Comment(7)
This is only for iOS 11+... How can I fix it in earlier iOS versions?Softy
if you can read a little Swift code, give a look at this link. This is a Complete Guide to Implementing WKWebView samwize.com/2016/06/08/complete-guide-to-implementing-wkwebview. If not, tell me ;)Nore
Happens that I implemented my code, using same tutorial link you provided, but it's not working... check my code, even handleError method is the sameSofty
Yes, i see. Seem like you fotget to add webView.UIDelegate = self. Is this right?Nore
Tried setting UIDelegate without luckSofty
Did you fixed it? If not, can you create a github project and give me the link. I want to try debug it.Nore
Congratulation guy @Softy ; )Nore
G
0

This worked for me:

func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
    handleError(error: error as NSError)
}

func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
    handleError(error: error as NSError)
}

func handleError(error: NSError) {
    if let failingUrl = error.userInfo[NSURLErrorFailingURLStringErrorKey] as? String {
        if let url = NSURL(string: failingUrl) {
            UIApplication.shared.openURL(url as URL)
        }
    }
}
Genny answered 13/2, 2019 at 8:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.