WKWebView links not working - ios

I have build a Swift iOS app with the only purpose to display a website with the help of WKWebView.
This works fine, but pressing on links (e.g. on an [mailto:] button) doesn't work because it can't open anything!
Does anybody have a solution for this?
I have read a lot about solving this, but I don't know where to start.
Thanks for the help
[UPDATE: ]
The code below shows the solution
import UIKit
import WebKit
class ViewController: UIViewController, WKNavigationDelegate {
#IBOutlet var containerView : UIView! = nil
var webView: WKWebView?
override func loadView() {
super.loadView()
self.webView = WKWebView()
self.view = self.webView!
}
override func viewDidLoad() {
super.viewDidLoad()
webView?.navigationDelegate = self
let url = NSURL(string:"https://google.de")
let req = NSURLRequest (URL: url!)
self.webView!.loadRequest(req)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func prefersStatusBarHidden() -> Bool {
return true
}
func webView(webView: WKWebView, decidePolicyForNavigationAction navigationAction: WKNavigationAction, decisionHandler: (WKNavigationActionPolicy) -> Void) {
let url = navigationAction.request.URL?.absoluteString
let url_elements = url!.componentsSeparatedByString(":")
switch url_elements[0] {
case "mailto":
openCustomApp("mailto://", additional_info: url_elements[1])
decisionHandler(.Cancel)
default:
}
decisionHandler(.Allow)
}
func openCustomApp(urlScheme:String, additional_info:String){
if let requestUrl:NSURL = NSURL(string:"\(urlScheme)"+"\(additional_info)") {
let application:UIApplication = UIApplication.sharedApplication()
if application.canOpenURL(requestUrl) {
application.openURL(requestUrl)
}
}
}
}

You have to implement the following method (part of WebKit), which can be used to parse such URLs:
func webView(webView: WKWebView, decidePolicyForNavigationAction navigationAction: WKNavigationAction, decisionHandler: (WKNavigationActionPolicy) -> Void)
The idea is to write if-statements to check for certain URL types before passing the decision. More info here:
GitHub example
SO thread
Awesome tutorials for beginners with WKWebView at hackingwithswift
Keep in mind that some of the syntax for delegates has changed slightly in Swift 3 - something to keep in mind when using tutorials. If you have already found this but you ran into an error - post your code.
EDIT: just an update for others who stumble upon this question. Keep in mind that you need the right formatting for mailto e.g. mailto://tes#test.com (as mentioned by OhadM in the comments of the OP)

Related

Making certain links in WKWebView open in Safari, not the Webview

I have a WebView app which contains external links that I wish to have users open within Safari as opposed to the webview itself, on tap. I believe it has something to do with a Navigation Delegate but I am new to iOS Dev and have no idea where to start! Below is my code as it is today. If you can tell me specifically what changes to make and where to put in any code, that would make my life so much easier. Thanks everyone in advance for any help! I think there's a way along the lines of setting a Navigation delegate such that all URL's that start with https://example-root.com/ open normal, in the webview since they are native nav buttons but all other URL's I want to open in safari on tap.
import UIKit
import WebKit
class ViewController: UIViewController {
let webView: WKWebView = {
let prefs = WKWebpagePreferences()
prefs.allowsContentJavaScript = true
let configuration = WKWebViewConfiguration()
configuration.defaultWebpagePreferences = prefs
let webView = WKWebView(frame: .zero, configuration: configuration)
return webView
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(webView)
// Do any additional setup after loading the view.
guard let url = URL(string: "https://example-root.com/") else {
return
}
webView.load(URLRequest(url: url))
DispatchQueue.main.asyncAfter(deadline: .now()+5) {
self.webView.evaluateJavaScript("document.body.innerHTML") { result, error in guard lethtml = result as? String, error == nil else {
return
}
}
}
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
webView.frame = view.bounds
}
}
You're right that you'll need to use the NavigationDelegate to intercept the navigation action. Make your ViewController conform to WKNavigationDelegate and implement the webView(_:decidePolicyFor:decisionHandler:) method:
extension ViewController: WKNavigationDelegate {
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: #escaping (WKNavigationActionPolicy) -> Void) {
if let url = navigationAction.request.url, !url.host.contains("example-root.com") {
UIApplication.shared.open(url)
decisionHandler(.cancel)
return
}
decisionHandler(.allow)
}
}
Don't forget to set the navigationDelegate property of your WKWebView, you can do this in viewDidLoad with the following:
webView.navigationDelegate = self

WkWebview redirect url issue to organisation login page

I am creating simple iOS application with Swift 4 using WKWebView and here WKWebView URL needs first to authenticate user using Microsoft way by entering email address and then it navigates to organisation login page. This is working fine on actual device browser, even on Simulator but WKWebView is not helping to redirect to organisation login page in actual device app. Does anyone encountered this type of issue with actual device. Please help or clue to fix this issue.
Implemented code:
import UIKit
import WebKit
class ViewController: UIViewController, WKUIDelegate, WKNavigationDelegate {
var webView: WKWebView!
override func loadView() {
let webConfiguration = WKWebViewConfiguration()
self.webView = WKWebView(frame: .zero, configuration: webConfiguration)
self.webView.navigationDelegate = self
self.webView.uiDelegate = self
view = self.webView
}
override func viewDidLoad() {
super.viewDidLoad()
let request = URLRequest(url: URL(string: "https://<App URL>")!)
self.webView.load(request)
}
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: #escaping (WKNavigationActionPolicy) -> Void) {
decisionHandler(.allow)
}
}

How do I open phone from tel: url link from WebKit iOS app using Swift

I am creating an iPhone app and in part using WebKit to display a web page in the app. Currently, when I click a button that is displayed on the website it should open the phone app (with a tel: link) and dial the number. Unfortunately in the app, it does nothing, however, it works fine in chrome/ safari.
I have tried to scour the internet and attempted several suggestions but nothing seems to do anything so far.
import UIKit
import WebKit
class ShopViewController: UIViewController, WKNavigationDelegate, WKUIDelegate {
#IBOutlet weak var backButton: UIBarButtonItem!
#IBOutlet weak var shopWebKit: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
shopWebKit.navigationDelegate = self
shopWebKit.uiDelegate = self
// Do any additional setup after loading the view.
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear( animated )
let urlString:String = "https://www.somewebsite.com"
let url:URL = URL(string: urlString)!
let urlRequest:URLRequest = URLRequest(url: url)
shopWebKit.load(urlRequest)
}
#IBAction func backButtonTapped(_ sender: Any) {
if shopWebKit.canGoBack{
shopWebKit.goBack()
}
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
backButton.isEnabled = webView.canGoBack
}
func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
if navigationAction.targetFrame == nil {
webView.load(navigationAction.request)
}
return nil
}
}
I am hoping to be able to click any tel: link button loaded in the WebKit and have it open the phone app and dial the number. Please help.
Try below code it's working fine swift5. first add 'navigationDelegate' to your WKWebView.
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: (WKNavigationActionPolicy) -> Void) {
switch navigationAction.request.url?.scheme {
case "tel":
UIApplication.shared.open(navigationAction.request.url!, options: [:], completionHandler: nil)
decisionHandler(.cancel)
break
default:
decisionHandler(.allow)
break
}
}
Note: try testing with actual device.
Here is just a code to open the phone app and make calls. Probably it can help u.
if let url = URL(string: "tel:+100000000"), UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url, options: [:], completionHandler: nil)
}

WKWebView issues with tel: links

I've looked though a lot of the answers for this question but they seem to be outdated now and none of the solutions are working for me and just give lots of errors.
I'm just gettign into xcode and apps and am loading a local html file "index.html" into a WKWebView. This is fine, loads fine, displays as it should, BUT I have some tel: links on the page which don't work, I tap on them and nothing. I am using fraework7 for my html files and have added the "external" class, which works in safari etc.
UPDATE: Using the following code I can click on a link and it will try and call, but then gives a fatal error
import UIKit
import WebKit
class ViewController: UIViewController, WKNavigationDelegate{
#IBOutlet weak var webview: WKWebView!
override func viewDidLoad() {
webview.navigationDelegate = self
super.viewDidLoad()
let htmlpath = Bundle.main.path(forResource: "index", ofType: "html")
let url = URL(fileURLWithPath: htmlpath!)
let request = URLRequest(url: url)
webview.load(request)
webview.scrollView.bounces = false
webview.configuration.dataDetectorTypes = .phoneNumber
// Do any additional setup after loading the view, typically from a nib.
}
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: #escaping (WKNavigationActionPolicy) -> Swift.Void) {
if navigationAction.request.url?.scheme == "tel" {
UIApplication.shared.openURL(navigationAction.request.url!)
decisionHandler(.cancel)
}
decisionHandler(.allow)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
and html looks like:
<tr>
<td>Police / Fire / Ambulance</td>
<td>Emergency Services</td>
<td>999</td>
</tr>
obviously part of a larger table but this is the example.
Any pointers would be greatly appreciated as I've been going round in circles for ages.
Managed to find a solution finally:
import UIKit
import WebKit
class ViewController: UIViewController, WKNavigationDelegate, WKUIDelegate{
#IBOutlet weak var webView: WKWebView!
override func viewDidLoad() {
webView.navigationDelegate = self
webView.uiDelegate = self
super.viewDidLoad()
let htmlpath = Bundle.main.path(forResource: "index", ofType: "html")
let url2 = URL(fileURLWithPath: htmlpath!)
let request = URLRequest(url: url2)
webView.load(request)
webView.scrollView.bounces = false
//webview.configuration.dataDetectorTypes = .phoneNumber
}
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: #escaping (WKNavigationActionPolicy) -> Void) {
if navigationAction.request.url?.scheme == "tel" {
UIApplication.shared.openURL(navigationAction.request.url!)
decisionHandler(.cancel)
} else {
decisionHandler(.allow)
}
}
Using this seems to pick up the tel: links fine, an answer I found on here previously didn't have the else statement so fired decisionHandler more than once which created the error, the else statement fixed that and now seems fine.
It's not just tel: links that aren't handled out of the box by WKWebView. Other links like mailto: and facetime: aren't handled either. For a complete list of these kinds of special links, see Apple's documentation.
You should decide which of Apple's special links you want handled (see the link above), and override the navigation handler in a manner similar to the following:
webView.navigationDelegate = self
...
extension ViewController: WKNavigationDelegate {
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: #escaping (WKNavigationActionPolicy) -> Void) {
guard let url = navigationAction.request.url else {
decisionHandler(.allow)
return
}
if ["tel", "sms", "facetime"].contains(url.scheme) && UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url, options: [:], completionHandler: nil)
decisionHandler(.cancel)
} else {
decisionHandler(.allow)
}
}
}
The only reason I can think of why Apple didn't include this in the first place is because when you try launching one of these links in Mobile Safari, it shows you a dialog such as the following:
This website has been blocked from automatically starting a call.
[Ignore] [Allow Call]
Thus, they may expect you want to implement something similar in your app instead of just launching the call.
Note however, that each type of link behaves differently:
tel: dialog that shows the phone number with call/cancel buttons.
sms: launches the Messages app.
mailto: launches the Mail app.
So if you do want to have some kind of modal that allows the user to cancel if they didn't mean to perform the action, depending on which actions you support it might already have similar behavior (e.g. tel:).
Just use WKNavigationDelegate and dataDetectorTypes for WKWebView configuration like below:
webView.navigationDelegate = self
webView.configuration.dataDetectorTypes = [.link, .phoneNumber]
extension PDFWebViewController: WKNavigationDelegate {
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: #escaping (WKNavigationActionPolicy) -> Void) {
if let requestUrl = navigationAction.request.url, requestUrl.scheme == "tel" {
UIApplication.shared.open(requestUrl, options: [:], completionHandler: nil)
decisionHandler(.cancel)
} else {
decisionHandler(.allow)
}
}
}
While suggested answers work fine - I would recommend to use white-list approach instead of black-list. We know that WKWebView can only handle http/https links so we can gracefully handle the error of opening any other kind of link.
func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
self.handleNavigationError(error)
}
func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
self.handleNavigationError(error)
}
private func handleNavigationError(_ error: Error) {
if let failedUrl = (error as NSError).userInfo[NSURLErrorFailingURLErrorKey] as? URL, failedUrlScheme = failedUrl.scheme?.lowercased(), !["http", "https"].contains(failedUrlScheme) {
UIApplication.shared.open(failedUrl, completionHandler: nil)
} else {
// handle other errors if needed
}
}
In this case you will support all the types of external app links like tel:, sms:, faceTime:, itms-services: etc.

WKWebView goBack requiring two invocations

I'm working on a iOS app that uses WKWebView, with custom navigation buttons. We're finding that following some links will require using our Back button twice, as the first invocation seems to just reload the current page. This does not happen in mobile Safari.
What could cause goBack() to need to be called twice to actually navigate back one page, while it works correctly in Safari? Are there changes I can make in the app to correct the issue?
Note: I cannot make changes to the web site's content or structure, so any fixes would have to be in the app.
Update:
It appears the site is using JavaScript for some of its content loads. The observeValue method is not invoked at all when this happens and is probably the culprit.
import UIKit
import WebKit
import SafariServices
class LandingPageViewController: UIViewController, SFSafariViewControllerDelegate, WKUIDelegate, WKNavigationDelegate {
#IBOutlet var urlNavigationItem: UINavigationItem!
#IBOutlet var backButton: UIBarButtonItem!
#IBOutlet var forwardButton: UIBarButtonItem!
#IBOutlet var reloadButton: UIBarButtonItem!
var webView: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
webView = WKWebView()
view.addSubview(webView)
webView.addObserver(self, forKeyPath: "loading", options: .new, context: nil)
let request = URLRequest(url: url) //url is defined elsewhere
webView.navigationDelegate = self
webView.load(request)
webView.allowsBackForwardNavigationGestures = true
webView.uiDelegate = self
backButton.isEnabled = false
forwardButton.isEnabled = false
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if(keyPath == "loading"){
backButton.isEnabled = webView.canGoBack
forwardButton.isEnabled = webView.canGoForward
}
}
#IBAction func back(_ sender: UIBarButtonItem) {
webView.goBack()
}
#IBAction func forward(_ sender: UIBarButtonItem){
webView.goForward()
}
#IBAction func reload(_ sender: UIBarButtonItem) {
let request = URLRequest(url: webView.url!)
webView.load(request)
}
func webView(_ webView: WKWebView,
decidePolicyFor navigationAction: WKNavigationAction,
decisionHandler: #escaping (WKNavigationActionPolicy) -> Void) {
// Custom logic here
}
}
try with following delegate methods,
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: #escaping (WKNavigationActionPolicy) -> Void) {
decisionHandler(.allow)
}
func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: #escaping (WKNavigationResponsePolicy) -> Void) {
decisionHandler(.allow)
}
You have implemented one but not call decisionHandler(.allow)
I was facing the same issue. What worked for me was stopping the navigation and then go back.
func goBack() {
self.webView.stopLoading()
if (self.webView.canGoBack) {
self.webView.goBack()
return;
}
}

Resources