When can I use a SFSafariViewController, WKWebView, or UIWebView with universal links?

I'm not sure I fully understand your question.

Just to cover basics, I'll allow Apple to explain what a Universal Link is:

Universal links let users open your app when they tap links to your website within WKWebView and UIWebView views and Safari pages, in addition to links that result in a call to openURL:, such as those that occur in Mail, Messages, and other apps.

So basically, imagine you're working on a Twitter client, and going through the timeline, you encounter a tweet with the following YouTube video link: https://youtu.be/ejQod8liXm0

If you tap it, since it's an HTTP URL, you'd expect Safari to open the link. However, I think we all iOS developers can pretty much all agree on the fact that native apps are better, so a better user experience would be for the official YouTube app to open the video, even from your third-party client.

This is what "Universal Links" allow; if the YouTube app registers itself on iOS as the app responsible for handling https://www.youtube.com(*) links, everyone in the iOS ecosystem benefits if we follow the API rules. The same thing happens if you run a website and you want any of your content to trigger your official iOS app to open if your user has it installed, or for the user to be prompted if they'd like to install it for a better user experience (and to help your business, of course).

So, with all of this out of the way, let's get back to the original text:

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 from within an embedded SFSafariViewController, WKWebView, or UIWebView object, iOS opens your app.

What this means is that, if from your app, your user taps on a Universal Link to your content (example: http://www.your-company.com/foo), and you do not detect this in your app's code via a regular expression for example, and instead instantiate a SFSafariViewController, WKWebView or UIWebView to open it as if it were a regular link to The New York Times or something, the OS will understand that instead of opening this in your app, you want Safari to handle this for you. Remember: the whole purpose of Universal Links is for the user to get a better experience by having the URL being handled by a native app, not by the browser.

This is what the first sentence says. As for the second, it clears up a potential follow-up question: what if the user taps on a Universal Link to my content from an embedded browser from another app that's not mine? Then, the sentence says, the OS will behave normally: it will open your app, respecting Universal Link rules.

TL;DR: You need to detect Universal Links to your content from your app too, in code, and handle them. iOS will not handle this for you. Instead, if you tell the OS to open a Universal Link to your app's content in an embedded browser from your app, it will do exactly as told.

EDIT: If you need help deciding between asking SFSafariViewController or the -openURL:options:completitionHandler: APIs to open a URL, this link should help you; my recommendation would be to use -openURL:options:completitionHandler: first, and if it's not successful, then use SFSafariViewController or similar.

Hope this helps and that I've correctly understood your question.

Cheers! And Happy iOS 11 programming! :)


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