Search code examples
iosswiftwkwebviewprogressive-web-appsmailto

WKWebView: mailto links in html content not opening mail app


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:[email protected]">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)
    }
}

Solution

  • 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 protected]"
            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 protected]">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..