13
votes

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!

3
Sounds like it might be a bug? I'd recommend raising it with Apple via bugreport.apple.comGlen T
Have you got any solution?AntiMoron
I haven't been working on that project lately, so I don't know if anything has changed. We left it with having to hit the home button and reenter the app to get the link. It's relatively rare for our users so doing more was not useful. I personally have not submitted it as a bug to Apple. Sorry I can't hep more just now :-(emrys57

3 Answers

1
votes

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,

0
votes

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.

-1
votes

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)
        }
    }
}