WKWebView click event - ios

I am opening youtube on WKWebView. When I click the one of the row I can listen music on webview. I only want to get selected row url and do not want to open player or other pages. I can get the selected row url but I couldn't prevent the page from opening.
This is my code and it is not working :(
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: #escaping (WKNavigationActionPolicy) -> Void) {
let clickUrl = navigationAction.sourceFrame.webView?.url
if let url = navigationAction.request.url?.absoluteString
{
if clickUrl != nil && ((clickUrl?.absoluteString.range(of: "watch")) != nil)
{
self.openNewPage() //This is my page and I want to open this one instead of youtube player
decisionHandler(.cancel)
return
}
else
{
decisionHandler(.allow)
}
}
else
{
decisionHandler(.allow)
}
}
Thanks,
Jessica

Related

Swift 5 Disable Redirection from WKWebView to different Apps

I have a WKWebView inside my app. The problem is that when for example going on https://stockx.com/de-de and the user has the StockX app, the user is redirected to the app.
Is there any way to disable this redirection in genreal for my webView? I couldn't find anything on this... Not too sure if you need code but here is my webView:
lazy var webView: WKWebView = {
let webConfiguration = WKWebViewConfiguration()
let webView = WKWebView(frame: .zero, configuration: webConfiguration)
webView.uiDelegate = self
webView.translatesAutoresizingMaskIntoConstraints = false
return webView
}()
Apparently there is this function for an older Swift version:
func webView(webView: WKWebView, decidePolicyForNavigationAction navigationAction: WKNavigationAction, decisionHandler: ((WKNavigationActionPolicy) -> Void)) {
print("webView:\(webView) decidePolicyForNavigationAction:\(navigationAction) decisionHandler:\(decisionHandler)")
let app = UIApplication.sharedApplication()
let url = navigationAction.request.URL
let myScheme: NSString = "https"
if (url!.scheme == myScheme) && app.canOpenURL(url!) {
print("redirect detected..")
// intercepting redirect, do whatever you want
app.openURL(url!) // open the original url
decisionHandler(.Cancel)
return
}
decisionHandler(.Allow)
}
But this is no longer working in Swift 5. Any idea how to to this in Swift 5?
So what is happening here it's web app initializing universal link. What you can theoretically do to block this:
Reference to this answer
func webView(webView: WKWebView, decidePolicyForNavigationAction navigationAction: WKNavigationAction, decisionHandler: ((WKNavigationActionPolicy) -> Void)) {
print("webView:\(webView) decidePolicyForNavigationAction:\(navigationAction) decisionHandler:\(decisionHandler)")
let app = UIApplication.sharedApplication()
let url = navigationAction.request.URL
let myScheme: NSString = "https"
if (url!.scheme == myScheme) && app.canOpenURL(url!) {
print("redirect detected..")
// intercepting redirect, do whatever you want
app.openURL(url!) // open the original url
decisionHandler(.Cancel)
return
}
decisionHandler(.Allow)
}

How to pass function to webpage using swift?

I have a situation where I need to capture an event from a button click in a WebPage.
this code is on the webpage on a button click.
okCoolClick = () => {
/*eslint-disable* /
console.log("okay cool ", window.okCool);
if (window.okCool) {
okCool.performClick();
} else {
window.open("some-url");
}
};
Now the Android team is done with the problem, with below code.
commonWebView.addJavascriptInterface(object : Any() {
#JavascriptInterface
fun performClick() {
val ownerId = arguments?.getInt(OWNER_ID)
if (ownerId == -1) {
context?.startActivity<RightNavigationActivity>()
activity?.finishAffinity()
} else {
context?.startActivity<HomeItemListActivity>(
Pair(HomeItemListActivity.INTENT_EXTRA_COMPONENT,
AppConstants.HOME_SUB_LIST_CLUB_OUTLET_LIST),
Pair(HomeItemListActivity.INTENT_EXTRA_CLUB_ID, ownerId),
Pair(HomeItemListActivity.INDUSTRY_ID, -1)
)
activity?.finish()
}
}
}, "okCool")
So, Now I'm left with finding the solution in iOS,
How can I achieve this on my iOS project? Any help or node toward the correct direction would be great.
In ViewDidLoad() add this
let config = WKWebViewConfiguration()
let contentController = WKUserContentController()
contentController.add(self, name: "function")
config.userContentController = contentController
webView = WKWebView(frame: .zero, configuration: config)
add two methods from WKNavigationDelegate delegate:
func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: #escaping (WKNavigationResponsePolicy) -> Void) {
return decisionHandler(.allow)
}
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: #escaping (WKNavigationActionPolicy) -> Void)
{
if navigationAction.navigationType == WKNavigationType.linkActivated, let url = navigationAction.request.url{
DeepLink.deepLink(url.absoluteString)
}
return decisionHandler(.allow)
}
Finally add the most important method to get callback from WKScriptMessageHandler delegate :
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage){
let dataFromWeb = message.body //Data Passed from webview
}
Pass data from html like this
var myObj = {"name":"John", "age":30, "car":null};
window.webkit.messageHandlers.function.postMessage(myObj);
"function" name should be same in the above statement and when u add userContentController as a config for your webview.
You can use this WebViewJavascriptBridge for sending messages between Obj-C/Swift and JavaScript.
Also you can do it without using any Third Party read this.

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.

Sharing web content on Facebook and Twitter not working - WebKit iOS Swift

I have a problem with sharing web content to facebook and twitter from my app which based on loading webpages into WebKit. In the webpage, I have 5 buttons: Facebook, Twitter, WhatsApp, Viber and SMS. All buttons except Facebook and Twitter works well. When the user clicks on WhatsApp, Viber and SMS, method "decidePolicyFor" is calling and I can to decide what will happen. Clicks on the Facebook and Twitter buttons runs nothing. There is no results and no errors.
I have also tried with implementing new QueriesSchemes into info.plist file but no results.
<key>LSApplicationQueriesSchemes</key>
<array>
<string>twitter</string>
<string>fb</string>
<string>sms</string>
<string>viber</string>
<string>whatsapp</string>
</array>
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: #escaping (WKNavigationActionPolicy) -> Void) {
if navigationAction.navigationType == .linkActivated {
print("user click")
if let newURL = navigationAction.request.url,
let host = newURL.host , !host.hasPrefix("-mywebsite-") && UIApplication.shared.canOpenURL(newURL) && UIApplication.shared.openURL(newURL) {
print(newURL)
print("Redirected to browser. No need to open it locally")
decisionHandler(.cancel)
} else {
print("Open it locally")
let url = navigationAction.request.url?.absoluteString
let url_elements = url!.components(separatedBy: ":")
if url_elements[0] == "sms" {
print("sms")
openCustomApp(urlScheme: "sms://", additional_info: url_elements[1])
decisionHandler(.cancel)
} else if url_elements[0] == "whatsapp"{
print("whatsapp")
openCustomApp(urlScheme: "whatsapp://", additional_info: url_elements[1])
decisionHandler(.cancel)
} else if url_elements[0] == "viber"{
print("viber")
openCustomApp(urlScheme: "viber://", additional_info: url_elements[1])
decisionHandler(.cancel)
} else {
print("unknown")
decisionHandler(.allow)
}
}
} else {
print("not a user click")
decisionHandler(.allow)
}
}
try this, its working fine..
func webView(_ webView: WKWebView, createWebViewWith configuration:
WKWebViewConfiguration, for navigationAction: WKNavigationAction,
windowFeatures: WKWindowFeatures) -> WKWebView? {
NSLog("Test method call from another class", "")
if (navigationAction.targetFrame == nil) {
if #available(iOS 10.0, *) {
UIApplication.shared.open(navigationAction.request.url!, options: [:],
completionHandler: nil)
} else {
UIApplication.shared.openURL(navigationAction.request.url!)
}
return nil
}
return nil;
}

Launching phone/email/map links in WKWebView

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
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);
}
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)
}
}
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.
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".
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)
}
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
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
}
}
}

Resources