14

I created a very simple iOS app (Swift 5). It's just a WKWebView that loads my PWA url.

Everything works fine except all <a href="mailto:name@name.com">Mail me</a> links. When I click them, nothing happens, my mail app doesn't open.

This is the code of my ViewController.swift:

//
//  ViewController.swift
//  panel
//
//  Created by kevin on 25/07/2019.
//  Copyright © 2019 umono. All rights reserved.
//

import UIKit
import WebKit

class ViewController: UIViewController, WKUIDelegate {
    
    var webView: WKWebView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let myURL = URL(string:"https://someUrlToMyApp.appspot.com")
        let myRequest = URLRequest(url: myURL!)
        webView.load(myRequest)
        
        if #available(iOS 11.0, *) {
            webView.scrollView.contentInsetAdjustmentBehavior = .never;
        }
        
    }
    
    override func loadView() {
        
        let webConfiguration = WKWebViewConfiguration()
        webConfiguration.dataDetectorTypes = [.all]
        webView = WKWebView(frame: .zero, configuration: webConfiguration)
        webView.uiDelegate = self
        view = webView
        
    }

}

EDIT:

Thx guy's, here is my working code:

//
//  ViewController.swift
//  panel
//
//  Created by kevin on 25/07/2019.
//  Copyright © 2019 umono. All rights reserved.
//

import UIKit
import WebKit

class ViewController: UIViewController, WKUIDelegate {
    
    var webView: WKWebView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let myURL = URL(string:"https://someUrlToMyApp.appspot.com")
        let myRequest = URLRequest(url: myURL!)
        webView.load(myRequest)
        
        if #available(iOS 11.0, *) {
            webView.scrollView.contentInsetAdjustmentBehavior = .never;
        }
        
        webView.navigationDelegate = self
        
    }
    
    override func loadView() {
        
        let webConfiguration = WKWebViewConfiguration()
        webConfiguration.dataDetectorTypes = [.all]
        webView = WKWebView(frame: .zero, configuration: webConfiguration)
        webView.uiDelegate = self
        view = webView
        
    }

}

extension ViewController: WKNavigationDelegate {
    func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction,
                 decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
        guard
            let url = navigationAction.request.url else {
                decisionHandler(.cancel)
                return
        }
        
        let string = url.absoluteString
        if (string.contains("mailto:")) {
            UIApplication.shared.open(url, options: [:], completionHandler: nil)
            decisionHandler(.cancel)

            return
        }
        decisionHandler(.allow)
    }
}
Paulo Mattos
  • 18,845
  • 10
  • 77
  • 85
kevinius
  • 4,232
  • 7
  • 48
  • 79

1 Answers1

16

One way to do what you want would be to implement WKNavigationDelegate:

import UIKit
import WebKit

class ViewController: UIViewController {

    @IBOutlet weak var webView: WKWebView!

    override func viewDidLoad() {
        super.viewDidLoad()

        guard
            let file = Bundle.main.path(forResource: "test", ofType: "html"),
            let html = try? String(contentsOfFile: file) else {
                return
        }

        webView.navigationDelegate = self
        webView.loadHTMLString(html, baseURL: nil)
    }

    @IBAction func didTapButton(_ sender: Any) {
        let email = "email@email.com"
        guard
            let url = URL(string: "mailto:\(email)") else {
                return
        }

        UIApplication.shared.open(url, options: [:], completionHandler: nil)
    }
}

extension ViewController: WKNavigationDelegate {
    func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction,
                 decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
        guard
            let url = navigationAction.request.url,
            let scheme = url.scheme else {
                decisionHandler(.cancel)
                return
        }

        if (scheme.lowercased() == "mailto") {
            UIApplication.shared.open(url, options: [:], completionHandler: nil)
            // here I decide to .cancel, do as you wish
            decisionHandler(.cancel)
            return
        }
        decisionHandler(.allow)
    }
}

Here you have a ViewController that has webView as an outlet, this WKWebView would load an html file like this:

<a href="mailto:email@email.com">Mail me</a>

And I also added in storyboard a button just for reference, which would have the IBAction didTapButton described above.

The key here is:

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

Which would give you the URL and let you decide what policy is suitable for it. Here I check if it contains mailto: as I already know this is what you're interested in so if it does, I simply open the URL as I would do if the user presses an UIButton visible on screen.

Hope it helps, cheers!

LE: Make sure you run on a real device (simulators don't have Mail app installed), also make sure you have the Mail app installed, cause I didn't..

Mihai Erős
  • 1,129
  • 1
  • 11
  • 18
  • Hi, thx... i tried to add your code (func webView) however, this doesn't change anything. I also added a print('test') below if (string.contains("mailto:")) to see if the code executes when i click the mailto link, but nothing prints in the console. – kevinius Jul 25 '19 at 12:53
  • 2
    @kevinius Did you set `webView.navigationDelegate = self` ? – TheTiger Jul 25 '19 at 13:05
  • 1
    Updated my original post to include the full working solution – kevinius Jul 25 '19 at 13:11
  • 2
    But this method will fail if your url contains `mailto` string anywhere in the url string. for example: `https://abcmailto.com`, it will open it outside the app according to condition. So better to match `url.scheme` instead of `contains` function. – TheTiger Jul 25 '19 at 13:17
  • @TheTiger you are right, however @kevinius should decide what policy should do after the Mail app is gonna open, I decided to `.cancel`. – Mihai Erős Jul 25 '19 at 13:38
  • What about if you're using a JavaScript function because you're doing some logging and conditional building of a subject and body, then calling `window.open(``mailto:email@email.com?subject=${subject}&body=${body}``, '_blank');` This does not appear to hit the delegate method. – jhelzer Aug 26 '20 at 00:02
  • Found this to be a semi elegant solution so I didn't have to go and modify all of my web code to use hrefs where applicable: https://blog.nextzy.me/ios-handling-popup-new-window-inside-web-view-c9c91b23ac2b – jhelzer Aug 26 '20 at 00:14