wkwebview still not handling universal link - ios

I have an iOS universal link targeted at myApp. When I click that link in another app, myApp opens and displays the right payoff perfectly, it's working.
But myApp includes a built-in browser using WKWebView. When I click the same universal link from within my built-in browser, iOS doesn't send the link to myApp, it goes and fetches a webpage.
Apple docs say
If you instantiate a SFSafariViewController, WKWebView, or UIWebView object to handle a universal link, iOS opens your website in Safari instead of opening your app. However, if the user taps a universal link within an embedded SFSafariViewController, WKWebView, or UIWebView object, iOS opens your app.
I note this similar question where the suggestion was to define a WKWebView delegate. I have both WKWebView delegates defined and in use in myApp, and it's not helping. This other question has lots of upvotes but no answers.
My WKWebView can actually open universal links to other apps. If I click the link https://open.spotify.com/artist/3hv9jJF3adDNsBSIQDqcjp from within the myApp WKWebView then it opens the spotify app without opening any intermediate webpage (even though the long-press menu in myApp doesn't offer to "open in spotify"). But iOS will not deliver the universal link to myApp when I click it from within myApp.
After much testing, I discover that I can prevent the display of a webpage associated with the universal link by looking for the specific URL and cancelling the display:
func webView(webView: WKWebView, decidePolicyForNavigationResponse navigationResponse: WKNavigationResponse, decisionHandler: (WKNavigationResponsePolicy) -> Void) {
if let urlString = navigationResponse.response.URL?.absoluteString {
if urlString.hasPrefix(myULPrefix) { // it's a universal link targetted at myApp
decisionHandler(.Cancel)
return
}
}
decisionHandler(.Allow)
}
I have to do this in the decision handler for the response, not the one for the action. When I do this, the universal link is queued for delivery to myApp. BUT it is not actually delivered by iOS until I quit my app (for example, by hitting the home button) and relaunch it. The appDelegate function specified as delivering this message in the Apple docs referenced above
func application(application: UIApplication, continueUserActivity userActivity: NSUserActivity, restorationHandler: ([AnyObject]?) -> Void) -> Bool {}
is not called. It is called when I do a more conventional deeplink - clicking a universal link in Safari to open myApp.
Is this a bug? Or a feature? Or am I, as usual, just barking up the wrong tree?
Thanks!

This will be the behavior if your wkwebview wep page domain name is same as your universal link domains name. In shot, If web page opened in Safari,SFSafariVC, WKWebView or UIWebView has domain same as your app's app link (universal link) domain it will not tirgger universal link,

I think Apple is made that this way because you can handle it more customizable when it happens when your application is running.
You found the place to put your own handler call:
func webView(webView: WKWebView, decidePolicyForNavigationResponse navigationResponse: WKNavigationResponse, decisionHandler: (WKNavigationResponsePolicy) -> Void) {
if let urlString = navigationResponse.response.URL?.absoluteString {
if urlString.hasPrefix(myULPrefix) { // it's a universal link targetted at myApp
decisionHandler(.Cancel)
// put here the call to handle the link, for example:
MyLinkHandler.handle(urlString)
return
}
}
decisionHandler(.Allow)
}
You can make any action in the background and the user stays on the opened page, or you can hide the web view and open another section of your application to show appropriate content.

I resolved this issue with my Swift 4 class below. It also uses embedded Safari Browser if possible. You can follow a similar method in your case too.
import UIKit
import SafariServices
class OpenLink {
static func inAnyNativeWay(url: URL, dontPreferEmbeddedBrowser: Bool = false) { // OPEN AS UNIVERSAL LINK IF EXISTS else : EMBEDDED or EXTERNAL
if #available(iOS 10.0, *) {
// Try to open with owner universal link app
UIApplication.shared.open(url, options: [UIApplication.OpenExternalURLOptionsKey.universalLinksOnly : true]) { (success) in
if !success {
if dontPreferEmbeddedBrowser {
withRegularWay(url: url)
} else {
inAnyNativeBrowser(url: url)
}
}
}
} else {
if dontPreferEmbeddedBrowser {
withRegularWay(url: url)
} else {
inAnyNativeBrowser(url: url)
}
}
}
private static func isItOkayToOpenUrlInSafariController(url: URL) -> Bool {
return url.host != nil && (url.scheme == "http" || url.scheme == "https") //url.host!.contains("twitter.com") == false
}
static func inAnyNativeBrowser(url: URL) { // EMBEDDED or EXTERNAL BROWSER
if isItOkayToOpenUrlInSafariController(url: url) {
inEmbeddedSafariController(url: url)
} else {
withRegularWay(url: url)
}
}
static func inEmbeddedSafariController(url: URL) { // EMBEDDED BROWSER ONLY
let vc = SFSafariViewController(url: url, entersReaderIfAvailable: false)
if #available(iOS 11.0, *) {
vc.dismissButtonStyle = SFSafariViewController.DismissButtonStyle.close
}
mainViewControllerReference.present(vc, animated: true)
}
static func withRegularWay(url: URL) { // EXTERNAL BROWSER ONLY
if #available(iOS 10.0, *) {
UIApplication.shared.open(url, options: [UIApplication.OpenExternalURLOptionsKey(rawValue: "no"):"options"]) { (good) in
if !good {
Logger.log(text: "There is no application on your device to open this link.")
}
}
} else {
UIApplication.shared.openURL(url)
}
}
}

Related

Disable Youtube app redirect from WKWebview in swift

I developed simple web browser with WKWebView in Swift.
When I click Youtube link, Youtube app is auto launched.
I hope to play Youtube video inside my web browser.
I don't need to launch Youtube app.
Please help me.
Here are my sample code.
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let webViewConfiguration = WKWebViewConfiguration()
webViewConfiguration.allowsInlineMediaPlayback = true
wkWebView.configuration.allowsInlineMediaPlayback = true
wkWebView.navigationDelegate = self
let myURL = URL(string: "https://www.google.com")
let youtubeRequest = URLRequest(url: myURL!)
wkWebView.load(youtubeRequest)
}
Yes, you can do this.
First you need to set up a WKNavigationDelegate in your webview, then in webview:decidePolicyFor method cancel any link activated youtube requests, and force it to load within your webview.
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: #escaping (WKNavigationActionPolicy) -> Void) {
if navigationAction.navigationType == .linkActivated && navigationAction.request.url?.host?.hasSuffix("youtube.com") {
decisionHandler(.cancel)
DispatchQueue.main.async {
webView.load(navigationAction.request)
}
} else {
decisionHandler(.allow)
}
}
Unfortunately you can't do that programatically. Following are the steps to achieve something like you want manually.
Open youtube app from Home screen
Tap on your profile picture on the upper right corner.
Go to Settings and tap on Google app Settings
Check the Safari Option.

Swift 3 UIWebView open (YouTube logo/watermark hyperlink from video) in Safari

Using Swift 3 and UIWebView how do I force the YouTube logo/watermark “Hyperlink” that is displayed in a YouTube video playing through a webpage IFrame to open in Safari?
I am using a UIWebView (can’t transition to WKWebView just yet.) to display a web page that plays a YouTube video in an IFrame.
Following the “YouTube Embedded Players and Player Parameters” instructions at https://developers.google.com/youtube/player_parameters?csw=1#modestbranding. I was able to remove the title and share button from the top of the video using showinfo=0. This only leaves the YouTube logo/watermark.
When you click on the YouTube logo/watermark it opens YouTube inside my app, which is not good for the user navigation experience.
If a user clicks the YouTube logo/watermark I need this link to open in Safari. I have tried using the code shown below without success. The code works on every other link in the UIWebView but not on the YouTube logo/watermark hyperlink embedded in the actual video.
// ViewController.swift
import UIKit
class ViewController: UIViewController, UIWebViewDelegate {
#IBOutlet var webView: UIWebView!
override func viewDidLoad() {
super.viewDidLoad()
webView.delegate = self
if let url = URL(string: "http://www.example.com") {
let request = URLRequest(url: url)
webView.loadRequest(request)
}
}
//Force all links to open in Safari.
func webView(_ webView: UIWebView, shouldStartLoadWith request: URLRequest, navigationType: UIWebViewNavigationType) -> Bool {
if let url = request.url, navigationType == UIWebViewNavigationType.linkClicked {
UIApplication.shared.openURL(url)
return false
}
return true
}}
I was able to achieve this in the android version of my app using the example at WebView link click open default browser.
I have spent a week trying to figure this out so as not to waste any ones time with a question that has already been answered. Any help in figuring this out would be appreciated. Thank you!
I'm looking for the exact same solution and I'm wondering if anyone can help on this matter
I tried it with the following code snippet
func webView(_ webView: UIWebView, shouldStartLoadWith request: URLRequest, navigationType: UIWebViewNavigationType) -> Bool {
if navigationType == UIWebViewNavigationType.linkClicked {
guard let abURL = request.url else { return true }
let abRequest = URLRequest(url: abURL)
print(abURL)
print(abRequest)
if abURL.absoluteString.range(of: "youtube") != nil {
// Open links in Safari
if #available(iOS 10.0, *) {
UIApplication.shared.open(abURL, options: [:], completionHandler: nil)
} else {
UIApplication.shared.openURL(abURL)
}
} else {
// Open links in Webview
webView.loadRequest(abRequest)
}
return false
}
return true
}
On line 5/6 I print the url if a link is clicked and when I click on that Youtube Watermark inside the video (included as a iframe) I don't get a print of that url so my guess is that swift doesn't "know" that a link is clicked. All the other links are working and printing the url.
I also included an iframe with just a link to youtube in it
Youtube Link
and this link works and I can print the url and it opens in safari so the problem must be with the link inside the youtube embed container
Try UIWebViewNavigationType.other to catch youtube.com/watch url insted of linkClicked:
func webView(_ webView: UIWebView, shouldStartLoadWith request: URLRequest, navigationType: UIWebViewNavigationType) -> Bool {
if let url = request.url, navigationType == UIWebViewNavigationType.other {
// catch "youtube.com/watch" and let safari to open link
return false
}
return true
}}

How can I programmatically determine if a URL is a Universal Link or just a regular web URL?

On iOS, how can I programmatically determine if a URL is a Universal Link or just a regular web URL?
Let's say you are about to launch the URL http://www.yelp.com from your own iOS app. (http://www.yelp.com is a fully registered universal link.)
Case one) the user doesn't have the app installed -> You want to show them the website in an IN-APP webview.
Case two) the user does have the app installed -> You want to launch out of your app and deep link directly to the the yelp app by using [[UIApplication sharedApplication] openURL:URL]; instead of presenting a webview in app.
Here is the problem:
All you get to work with is the string url: "http://www.yelp.com" Your goal is to launch out to the yelp app if installed but present an in-app webview if yelp is not installed.
Note 1: This question is only about universal links. Please do not give answers which use URL Schemes.
Note 2: This question is not about specifically launching the yelp app. The solution should work for any url to determine if it is a universal link of an installed app.
Can you do this?
Detect if a link is universal using UIApplicationOpenURLOptionUniversalLinksOnly
Following is the solution:
[[UIApplication sharedApplication] openURL:url
options:#{UIApplicationOpenURLOptionUniversalLinksOnly: #YES}
completionHandler:^(BOOL success){
if(!success) {
// present in app web view, the app is not installed
}
}];
I resolved this issue with my Swift 4 class below. It also uses embedded Safari Browser if possible. You can follow a similar method in your case too.
import UIKit
import SafariServices
class OpenLink {
static func inAnyNativeWay(url: URL, dontPreferEmbeddedBrowser: Bool = false) { // OPEN AS UNIVERSAL LINK IF EXISTS else : EMBEDDED or EXTERNAL
if #available(iOS 10.0, *) {
// Try to open with owner universal link app
UIApplication.shared.open(url, options: [UIApplication.OpenExternalURLOptionsKey.universalLinksOnly : true]) { (success) in
if !success {
if dontPreferEmbeddedBrowser {
withRegularWay(url: url)
} else {
inAnyNativeBrowser(url: url)
}
}
}
} else {
if dontPreferEmbeddedBrowser {
withRegularWay(url: url)
} else {
inAnyNativeBrowser(url: url)
}
}
}
private static func isItOkayToOpenUrlInSafariController(url: URL) -> Bool {
return url.host != nil && (url.scheme == "http" || url.scheme == "https") //url.host!.contains("twitter.com") == false
}
static func inAnyNativeBrowser(url: URL) { // EMBEDDED or EXTERNAL BROWSER
if isItOkayToOpenUrlInSafariController(url: url) {
inEmbeddedSafariController(url: url)
} else {
withRegularWay(url: url)
}
}
static func inEmbeddedSafariController(url: URL) { // EMBEDDED BROWSER ONLY
let vc = SFSafariViewController(url: url, entersReaderIfAvailable: false)
if #available(iOS 11.0, *) {
vc.dismissButtonStyle = SFSafariViewController.DismissButtonStyle.close
}
mainViewControllerReference.present(vc, animated: true)
}
static func withRegularWay(url: URL) { // EXTERNAL BROWSER ONLY
if #available(iOS 10.0, *) {
UIApplication.shared.open(url, options: [UIApplication.OpenExternalURLOptionsKey(rawValue: "no"):"options"]) { (good) in
if !good {
Logger.log(text: "There is no application on your device to open this link.")
}
}
} else {
UIApplication.shared.openURL(url)
}
}
}

Open the main app from a iOS Safari Share Extension

I have a Safari share extension where I want the ability to open the main app from within the extension. The user is presented with an alert where they have the option to open the app.
func openAppHandler() {
self.extensionContext?.completeRequest(returningItems: []) { (success) in
if let url = URL(string: "myapp://...") {
self.extensionContext?.open(url, completionHandler: nil)
}
}
}
The alert appears after the method didSelectPost() is called, and as you can see it occurs in the background priority completion block for the extension. The open method says in it's docs "In iOS 8, only the Today extension point (used for creating widgets) supports this method." I'm guessing it's still the case that it's still not supported in the Safari Share Extension.
Does anyone know of a way to open my main app from a share extension?
I found a solution here. I'm not sure if this is technically ok with Apple, but it works just as I need it to.
#objc func openURL(_ url: URL) {
return
}
func openContainerApp() {
var responder: UIResponder? = self as UIResponder
let selector = #selector(MyViewController.openURL(_:))
while responder != nil {
if responder!.responds(to: selector) && responder != self {
responder!.perform(selector, with: URL(string: "myapp://url")!)
return
}
responder = responder?.next
}
}

Wrapper with WKWebView - Load ALL links within WKWebView

WKWebView allows you to open external links (those different from base url) via Safari.However, sometimes you want to open ALL links within WKWebView itself.For instance, say you have a base url "https://trader.finmarkets.com" but you have a link "https://secure.transactions.finmarkets.com"that you also want to open within the WKWebView without using UIApplication.shared.open(url) which will open externally.
The question is : How do I wrap my website to open all links on the WKWebview?(of-course except email and telephone numbers)Thanks.
You could try something like this:
func webView(webView: WKWebView, decidePolicyForNavigationAction navigationAction: WKNavigationAction, decisionHandler: (WKNavigationActionPolicy) -> Void) {
if navigationAction.navigationType == .LinkActivated {
if let newURL = navigationAction.request.URL &&
UIApplication.sharedApplication().canOpenURL(newURL) &&
UIApplication.sharedApplication().openURL(newURL) {
print(newURL)
print("User clicked on link. Open it internally.")
decisionHandler(.Allow)
} else {
print("Cannot open URL")
}
} else {
print("Not a user click")
}
}
You may need to fiddle with this a little to get it to work perfectly, but I think it might be along the lines you're looking for.

Resources