iOS Universal Links

An extensive guide to deeplinks and how to use them

Anca

Anca

Senior Software Developer at Softvision
Anca

Latest posts by Anca

What are deeplinks and why/when would you need them?

As mobile app owners/creators, we want to design and develop applications that are simple and intuitive, thus increasing adoption and usage. Even if users install the app on their devices, we can’t control how often they open it, and how much time they spend in our app. And, sometimes, we want to make them go to the app and check important information… something like a heads-up.

What means do we have to accomplish this?
Well, we can send push notifications that will open our app when a user taps them.

There are two ways for adding deeplink support in our apps:

  1. Universal links: introduced in iOS 9, these are the same as the URL links to the website version of our app (that’s why they are referred to as “universal”). So, we can use the same URL link to link the user to either the mobile app or the website version. These links work only if there is a website version of our app. The website version existence is one more reason for us to find ways to engage users with the mobile app instead of the website, as the mobile app offers a better user experience on mobile.
  2. Custom URL schemes links: these can open your app only, and don’t depend on whether the website version exists or not. As the name suggests, these are sort of URL links that have custom schemes, other than HTTP or https.
    At the end of this document, you can find a comparison between the two approaches: universal links and custom URL scheme links.

How universal links work – a user experience

Tapping on a website link (in an email/SMS) will open it in the mobile app if that was the user’s most recent choice, and link the user to the corresponding screen. Otherwise, that link will open in the browser.

NOTE: If the mobile app is not installed on the device, the link will open in the browser


So, yes, the OS will remember and use the user’s most recent choice to decide whether to open that link in the browser or in the mobile app.

As per Apple doc:

“When a user taps a universal link that you handle, iOS also examines the user’s recent choices to determine whether to open your app or your website. For example, a user who has tapped a universal link to open your app can later choose to open your website in Safari by tapping a breadcrumb button in the status bar. After the user makes this choice, iOS continues to open your website in Safari until the user chooses to open your app by tapping OPEN in the Smart App Banner on the webpage.”

When does that happen? Read on below:

  • Long press on a website link (in an email for example)

– If the user chooses to “Open in Safari”, then this link will open in the Safari browser, to the corresponding page.

– If the user chooses to “Open in ‘Groupon’”, then the link will open in the Groupon mobile app, to the corresponding screen.

– The iOS will remember the user’s choice and subsequent taps on the same website link will open it in the mobile app until the user chooses differently.

– You might notice that this is the same experience as when you right click on a .doc file and select one of the apps listed under “Open with…” (OpenOffice, MSWord, TextEdit, Preview, etc.)

  • Tap OPEN in a Smart App Banner:

If the link is opened in Safari, there will be a Smart App Banner at the top with an “OPEN” button (if it is not visible, then pull down the page).

By tapping the OPEN button, the user chooses to open that universal link in the mobile app and, from now on, that link will open in the mobile app.

 

 

  • Tap on breadcrumb button in the app status bar: 

If a link was opened in Safari, and the user tapped on the “OPEN” button in the Smart App Banner, then this would open the link in the mobile app. In this case, the mobile app will have at the top of the status bar a, so-called, “breadcrumb button to the associated website. It’ll also have a “Back to App” button (in here: “<Safari”)

If the user chooses to tap on the back button to Safari, then the user will be linked to the browser. The system doesn’t consider this a user choice. The next time the user taps on the same link it’ll still open in the mobile app.

If the user chooses to tap on the breadcrumb button, the user will be linked to the associated website and, from now on, that link will open in the browser.

To summarize the benefits of universal links:

  • They are unique (unlike custom URL schemes), just as URLs are unique
  • They are secure (read on below to learn how this is achieved)
  • They are flexible: if the app is not installed then the link will be redirected to the browser (therefore, there is no issue for the user)
  • Simple/universal: one URL works for both website and mobile app
  • Private: other apps can communicate with your app without needing to know whether your app is installed or not

How to add support for universal links in your app

Universal links have been introduced in iOS 9.
They are universal because the same URL link works for both mobile app and website.
The URL scheme for universal links can be HTTP or https.

Prepare the application for universal links

The app preparation has two main parts:
(1) Enable “Associated Domains” app capability (i.e. make mobile app aware of website)
(2) Create an apple-app-site-association file that should be added to website (i.e. make website aware of mobile app)
This serves to create a secure connection between the website and the mobile app.

  1. Enable associated domains
    So that the app will be associated with one or more domains to access specific services, such as Safari saved passwords and activity continuation.
    By doing this, only links that match specified domains will go to the mobile app.
    Read more about adding app capabilities.a. Turn “Associated Domains” to ON, in-app Capabilities:


    b. This will add an entitlement to your app where you’ll add a list of domains that you want to handle as universal links  (entries like “applinks:domain”)
  • Sample: “applinks:www.softvision.com”
  • Don’t add more than 20 to 30 domains
  • Use * to specify all subdomains: “applinks:*.softvision.com” (would include: www.softvision.com, example.softvision.com) but be aware that www.softvision.com won’t match softvision.com. If you want to handle www.softvision.com and softvision.com as universal links, two separate entries should be added.

You’ll notice the second step is in red, about adding the Associated Domains to your App ID. This means “SVLibrary” requires a provisioning profile with Associated Domains feature enabled (this step is part of the guaranteed security around universal links).

So, enable “Associated Domains” feature for your App ID, like this:

And regenerate the provisioning profile to use with App ID.

Now, everything is set for the “Associated Domains” capability:

c. You can have different entitlements for different build configurations: one for release configuration/scheme, another one for debug configuration, etc.

  1. apple-app-site-association file

This is a JSON file, with exactly this name: “apple-app-site-association”, without any extension, that should contain the paths from the website that should be handled as universal links. It can also contain paths that should not be handled as universal links. Only links matching paths in this file can open in the app; other URL components, like query parameters or fragments, are ignored.

Create the file like this:
{  
  "applinks":{
     "apps": [ ],  
     "details":[  
        {
           "appID":"V3WEN3K4CT.com.softvision.SVLibrary",
           "paths":[
              "/library/",
              "/library/tutorials",
              "/library/articles",
              "/library/books",
"NOT /library/users/*"
           ]
        }
     ]
  }
}


Rules:

Upload the file: Other apple-app-site-association file samples:
www.linkedin.com/apple-app-site-association
www.facebook.com/apple-app-site-association
www.groupon.com/apple-app-site-association

This apple-app-site-association file should be uploaded at the root of your HTTPS web server or to a “.well-known” subdirectory. This file needs to be accessible via HTTPS without any redirects at:
https://<associated domain>/apple-app-site-association

or
https://<associated domain>/.well-known/apple-app-site-association

Different files can be uploaded for different domains.
For example: www.apple.com and developer.apple.com are different and can have different files, whereas apple.com, www.apple.com are the same.

Coupling all together:

When the mobile app is installed and before launching it, the iOS will try to access and download the apple-app-site-association file(s) located at the specified associated domain(s), for instance: iOS will try to access and download https://www.softvision.com/apple-app-site-association file.

Also, iOS makes sure that the website app allows the mobile app to open URLs on its behalf.

So, this is the one and only time that this file is downloaded from the root of the associated domain. From now on, whenever it’s needed, the system will look only at the local apple-app-site-association that was downloaded and stored at the installation time. This file is re-downloaded/refreshed on the next app update/re-install.

Any other future updates on apple-app-site-association (paths added/removed) will be available at the next app update.

Implementation details

iOS will open your app and call application:continueUserActivity:restorationHandler: whenever a user taps on a link to your website (associated with your app) within:

If the user is browsing your website in Safari and taps a universal link:

  • To a URL in the same domain: then iOS opens the link in Safari (it respects the user’s most likely intent)
  • To a URL in a different domain: then iOS opens the link in your app

If your app calls openURL, at some point, to open a universal link to your website, then the link will open in Safari and not in your app.

So, in your app delegate, implement application:continueUserActivity:restorationHandler: method so that your app can receive a universal link and handle it appropriately.

  • You’ll receive an NSUserActivity object with an activityType value of NSUserActivityTypeBrowsingWeb
  • The activity object’s webpageURL property contains the URL that the user is accessing
  • The webpageURL property always contains an HTTP or HTTPS URL
  • You can define a deeplink manager that should:
    • Define supported paths (that app can handle, remember these are in the apple-app-site-association file that is external to the app)
    • Check if incoming URL (in webpageURL property) matches any supported paths
    • If a match is found: route/navigate to the appropriate screen in the app
    • If no match is found: redirect to the website or display a message saying that the app can’t handle that link.
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([Any]?) -> Void) -> Bool {
       let result = false
       if userActivity.activityType == NSUserActivityTypeBrowsingWeb {
           if let webpageURL = userActivity.webpageURL {
               result = deeplinkManager.handleUniversalLink(url: webpageURL)
          }
       }
       return result
}

Sample deeplink manager:

enum ScreenName: Int {
    case home = 0
    case tutorials
    case articles
    case books
}

class DeeplinkManager {
    let supportedPaths: [String:ScreenName]
    static let sharedInstance = DeeplinkManager()
    
    private init() {
        supportedPaths = ["/library/": ScreenName.home,
                           "/library/tutorials": ScreenName.articles,
                           "/library/articles": ScreenName.tutorials,
                           "/library/books/": ScreenName.books
        ]
    }

    func handleUniversalLink(url: URL) -> Bool {
        guard let screenName = findScreenName(forDeeplink: url) else {
            // app doesn't recognize this link
            // 1. either redirect to web or 

// 2. display a message about app not being able to handle this link
            return false
        }
        
        navigateToScreen(screenName: screenName)

        return true
    }
    
    func findScreenName(forDeeplink deeplink: URL) -> ScreenName? {
        for (screenPath, screenName) in supportedPaths {
            if deeplink.absoluteString.contains(screenPath) {
                return screenName
            }
// you can use NSURLComponents APIs to manipulate the components of the URL or other
// 3rd parties to address more complex parsing/matching depending on your needs
        }
        return nil
    }

    func navigateToScreen(screenName: ScreenName) {
        switch screenName {
        case .home:
            showHomeScreen()
        case .tutorials:
            showTutorials()
        case .articles:
            showArticles()
        case .books:
            showBooks()
        }
    }
….
}

Testing, Troubleshooting & Limitations

  • Cannot be tested on simulators
  • You should check if apple-app-site-association file has successfully downloaded
  • Doesn’t work if you don’t have a website version of your app
  • Any updates (adds/removes) on apple-app-site-association file requires a new app release

How to add custom URL scheme deeplinks

You can also make your app handle custom URL schemes deeplinks.
For this, you should define the custom scheme for target: go to the “Info” tab and then to the “URL Types” section, like in the image below.

Sample custom scheme deeplink:  sv-library://www.softvision.com/books/*; sv-library://tutorials/*/reviews

So, whenever a user taps on a link that has “sv-library” as scheme, the iOS will launch the mobile app.
In the UIApplicationDelegate implement application:handleOpenURL: method to handle it appropriately:

Have a deeplink manager that should:

  • Define supported links that the mobile app can handle
  • Check if the incoming URL has “sv-library” as scheme
  • Parse for URL components in the incoming link. If it matches a supported path then it should route to the appropriate screen in the app.
func application(_ application: UIApplication, handleOpen url: URL) -> Bool {
       var result = false
       if let scheme = url.scheme, scheme == "sv-livrary" {
           result = deeplinkManager.handleCustomDeeplink(url: url)
       }
       return result
}   

In the above DeeplinkManager (the same can be used to handle both universal and custom links) implement the method to handle the incoming URL:

func handleCustomDeeplink(url: URL) -> Bool {
        guard let screenName = findScreenName(forDeeplink: url) else {
            // app doesn't recognize this link
            // 1. either redirect to web or

//  2. display a message about app not being able to handle this link
            return false
        }
        
        navigateToScreen(screenName: screenName)

        return true
    }

 

Comparison between universal links and custom URL schemes

References

Apple Universal Links
Adding capabilities
Managing App IDs

Share This Article


Anca

Anca

Senior Software Developer at Softvision
Anca

Latest posts by Anca

No Comments

Post A Comment