23

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

dfmuir
  • 2,048
  • 1
  • 17
  • 14
  • 2
    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. – matt Oct 22 '14 at 19:07

7 Answers7

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);
}
Darren Ehlers
  • 633
  • 6
  • 16
  • 2
    This code is a little verbose, and doesn't guarantee `decisionHandler` is called. – Aaron Brager Oct 22 '14 at 19:16
  • 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! – Darren Ehlers Oct 23 '14 at 13:00
  • 2
    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: https://github.com/dfmuir/KINWebBrowser/issues/10 – dfmuir Oct 25 '14 at 06:15
  • 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! – Darren Ehlers Oct 25 '14 at 22:56
  • 1
    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! – Stephen Paul Jan 06 '16 at 07:01
  • 2
    To avoid the security vulnerability issue that @dfmuir 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 call – Harry Wang Aug 25 '16 at 14:52
12

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)
    }

}
Vladyslav Zavalykhatko
  • 15,202
  • 8
  • 65
  • 100
Victor Almeida
  • 129
  • 1
  • 4
9

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.

2h4u
  • 504
  • 3
  • 8
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".

Abduhafiz
  • 3,318
  • 5
  • 38
  • 48
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)
}
Luca Davanzo
  • 21,000
  • 15
  • 120
  • 146
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

Iain Coleman
  • 166
  • 1
  • 13
-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
        }
    }
}
Ingo Karkat
  • 167,457
  • 16
  • 250
  • 324