Launching phone/email/map links in WKWebView
Asked Answered
M

7

24

KINWebBrowser is an open source web browser module for iOS apps. I recently upgraded KINWebBrowser to use WKWebView to begin phasing out UIWebView. This yields significant improvements, but:

Problem: WKWebView does not enable users to launch links containing URLs for phone numbers, email address, maps, etc.

How can I configure a WKWebView to launch the standard iOS behaviors for these alternate URLs when launched as links from the displayed page?

All of the code is available here

More info on WKWebKit

See the issue on the KINWebBrowser GitHub here

Mia answered 22/10, 2014 at 6:1 Comment(1)
You can't do it. If this functionality is important to you, that would be a reason for sticking with UIWebView for now - and for filing an enhancement request with Apple. There are a lot of things UIWebView can do that WKWebView can't do.Setscrew
P
27

I was able to get it to work for the Google Maps link (which appears to be related to the target="_blank") and for the tel: scheme by adding this function to your KINWebBrowserViewController.m

- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
{
    if(webView != self.wkWebView) {
        decisionHandler(WKNavigationActionPolicyAllow);
        return;
    }

    UIApplication *app = [UIApplication sharedApplication];
    NSURL         *url = navigationAction.request.URL;

    if (!navigationAction.targetFrame) {
        if ([app canOpenURL:url]) {
            [app openURL:url];
            decisionHandler(WKNavigationActionPolicyCancel);
            return;
        }
    }
    if ([url.scheme isEqualToString:@"tel"])
    {
        if ([app canOpenURL:url])
        {
            [app openURL:url];
            decisionHandler(WKNavigationActionPolicyCancel);
            return;
        }
    }
    decisionHandler(WKNavigationActionPolicyAllow);
}
Perinephrium answered 22/10, 2014 at 19:13 Comment(6)
This code is a little verbose, and doesn't guarantee decisionHandler is called.Fatigue
Good point! Thanks. This was a late night, last minute patch from 2 different sources thrown together to quickly solve a problem. While it did work, not the cleanest and there definitely were cases where decisionHandler wasn't being called. I've edited the code. Thx!Perinephrium
This code is definitely on the right path but it introduces a security vulnerability that may allow calls and FaceTime calls to be unwillingly initiated. Check out my explanation here: github.com/dfmuir/KINWebBrowser/issues/10Mia
Excellent catch. In my case, the app only displays closely controlled webpages (for internal information views), so we're currently safe. But excellent point all the same!Perinephrium
This solution has helped us tremendously at my company. Thank you Darren. We were able to modify it slightly to also open up the app store from a link from our web page and open the email app from tapping on a link in our web page as well. Cheers!Godroon
To avoid the security vulnerability issue that @Mia pointed out, change the url to "telprompt" instead of the default "tel". For example, if the original custom URL is "tel:(212)%20555-6666" then the telprompt equivalent is "telprompt:(212)%20555-6666". This way the user will get a prompt before making the callLoveland
H
13

Works on xcode 8.1, Swift 2.3.

For target="_blank", phone number (tel:) and email (mailto:) links.

func webView(webView: WKWebView, decidePolicyForNavigationAction navigationAction: WKNavigationAction, decisionHandler: (WKNavigationActionPolicy) -> Void) {
    if webView != self.webview {
        decisionHandler(.Allow)
        return
    }

    let app = UIApplication.sharedApplication()
    if let url = navigationAction.request.URL {
        // Handle target="_blank"
        if navigationAction.targetFrame == nil {
            if app.canOpenURL(url) {
                app.openURL(url)
                decisionHandler(.Cancel)
                return
            }
        }

        // Handle phone and email links
        if url.scheme == "tel" || url.scheme == "mailto" {
            if app.canOpenURL(url) {
                app.openURL(url)
                decisionHandler(.Cancel)
                return
            }
        }

        decisionHandler(.Allow)
    }
}

Updated for swift 4.0

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

    if webView != self.webView {
        decisionHandler(.allow)
        return
    }

    let app = UIApplication.shared
    if let url = navigationAction.request.url {
        // Handle target="_blank"
        if navigationAction.targetFrame == nil {
            if app.canOpenURL(url) {
                app.open(url)
                decisionHandler(.cancel)
                return
            }
        }

        // Handle phone and email links
        if url.scheme == "tel" || url.scheme == "mailto" {
            if app.canOpenURL(url) {
                app.open(url)
            }

            decisionHandler(.cancel)
            return
        }

        decisionHandler(.allow)
    }

}
Hornstein answered 22/11, 2016 at 13:2 Comment(1)
Don´t forget to add delegate: WKUIDelegate and var webView = WKWebView() to propertiesHornstein
J
10

You need to implemented an other callback to get this right (Swift 5.0):

// Gets called if webView cant handle URL
func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
  guard let failingUrlStr = (error as NSError).userInfo["NSErrorFailingURLStringKey"] as? String  else { return }
  let failingUrl = URL(string: failingUrlStr)!

  switch failingUrl {
    // Needed to open Facebook
    case _ where failingUrlStr.hasPrefix("fb:"):
    if #available(iOS 10.0, *) {
       UIApplication.shared.open(failingUrl, options: [:], completionHandler: nil)
       return
    } // Else: Do nothing, iOS 9 and earlier will handle this

  // Needed to open Mail-app
  case _ where failingUrlStr.hasPrefix("mailto:"):
    if UIApplication.shared.canOpenURL(failingUrl) {
      UIApplication.shared.open(failingUrl, options: [:], completionHandler: nil)
      return
    }

  // Needed to open Appstore-App
  case _ where failingUrlStr.hasPrefix("itmss://itunes.apple.com/"):
    if UIApplication.shared.canOpenURL(failingUrl) {
      UIApplication.shared.open(failingUrl, options: [:], completionHandler: nil)
      return
    }

  default: break
  }
}

Now Facebook, Mail, Appstore,.. getting called directly from your app without the need to open Safari

Edit: replaced custom startsWith() method with standard hasPrefix() method.

Joey answered 28/3, 2017 at 9:21 Comment(3)
What is startsWith?Lunisolar
Its the same as hasPrefix()Joey
but why tel scheme fails to load?Avivah
W
0

This helps me for Xcode 8 WKWebview

func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
    if navigationAction.targetFrame == nil {
        let url = navigationAction.request.url
        if url?.description.range(of: "http://") != nil || url?.description.range(of: "https://") != nil || url?.description.range(of: "mailto:") != nil || url?.description.range(of: "tel:") != nil  {
            UIApplication.shared.openURL(url!)
        }
    }
    return nil
}

EDITED:

In link must be attribute target="_blank".

Waldenses answered 3/10, 2016 at 9:6 Comment(0)
M
0

I'm landed here searching for how to open gmail attachments on wkwebview.

My solution is simple:

func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
    if navigationAction.targetFrame == nil, let redirect = navigationAction.request.url {
        if UIApplication.shared.canOpenURL(redirect) {
            self.webViewMail?.load(navigationAction.request)
            decisionHandler(.cancel)
            return
        }
    }
    decisionHandler(.allow)
}
Michaelmas answered 11/4, 2018 at 13:15 Comment(0)
I
0

UPDATE FOR SWIFT 4.2

Sorry to dig up an old post, but I was having the same issues and have updated the solution for Swift 4.2. I have put my solution here so that it may help others and if not I will hopefully find it the next time I am working with WKWebView!

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

    let url = navigationAction.request.url?.absoluteString
    let urlElements = url?.components(separatedBy: ":") ?? []

    switch urlElements[0] {

    case "tel":
        UIApplication.shared.openURL(navigationAction.request.url!)
        decisionHandler(.cancel)
    case "mailto":
        UIApplication.shared.openURL(navigationAction.request.url!)
        decisionHandler(.cancel)
    default:
        decisionHandler(.allow)
    }
}

I used the following site as inspiration:

SubzDesignz iOS Swift 4 WKWebview – Detect tel, mailto, target=”_blank” and CheckConnection

Imprisonment answered 26/11, 2018 at 11:49 Comment(0)
F
-1

Above answer workes for me, but I needed it to rewrite for swift 2.3

if navigationAction.targetFrame == nil {
    let url = navigationAction.request.mainDocumentURL
    if url?.description.rangeOfString("mailto:")?.startIndex != nil ||
        url?.description.rangeOfString("tel:")?.startIndex != nil
    {
        if #available(iOS 10, *) {
            UIApplication.sharedApplication().openURL(url!,options: [:], completionHandler: nil)
        } else {
            UIApplication.sharedApplication().openURL(url!)  // deprecated
        }
    }
}
Forsterite answered 7/11, 2016 at 12:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.