Universal links in iOS

Last day I was asked to integrate deep linking in one of the app that I made last year. It was in objective-C. I had to face so many issues doing this as there are no proper documentation and the one that is available over the internet are not straight forward. Here, I will share what I understood and how I did it.

Disclaimer: To make this article I had to read a lot of stuffs over the internet. Also, I had copied easy to understand examples and sentences from other article to make this article meaningful. 🤓 🤓 🤓

Apple docs link for universal links.

What Is A Deep Link?

A deep link is any link that directs a user past the home page of a website or app to content inside of it. e.g. linking directly to a product instead of the home page. For example the URL fb:// may open the Facebook app, but fb://profile/33138223345 opens Wikipedia’s profile in the Facebook app.

If you want to share a pair of shoes from the amazon.com with a friend, you can send a deep link that brings your friend directly to those shoes in the app. Without a deep link, your friend would have to find the amazon app on the App Store or Play Store, open the app to the homepage, locate the Search function, and then try to find the same pair of shoes you did.

Custom URI schemes were the original form of deep linking for mobile apps. They are like creating a “private internet” for your app, with links that look like myapp://path/to/content. The advantage of custom URI schemes is they are easy to set up and most apps already have one. The disadvantage is a user’s device only knows about this “private internet” if the corresponding app is already installed, and there is no graceful fallback option by default.

The workaround approach to deep linking with URI schemes involves using a traditional http:// link to launch a web browser. This link contains a JavaScript redirect to a custom URI scheme, which is executed by the web browser to launch the app. If the redirect attempt fails because the app is not installed, the JavaScript then takes the user to the App Store or Play Store.

This is still the primary approach to deep linking on Android, but Apple began blocking this approach on iOS in 2015 with the release of Universal Links.

What is Apple iOS Universal Links?

Apple introduced Universal Links in iOS 9 as a solution to the lack of graceful fallback functionality in custom URI scheme deep links. Universal Links are standard web links (http://mydomain.com) that point to both a web page and a piece of content inside an app. When a Universal Link is opened, iOS checks to see if any installed app is registered for that domain. If so, the app is launched immediately without ever loading the web page. If not, the web URL (which can be a simple redirect to the App Store) is loaded in Safari.

How Do Universal Links Work in iOS ?

Source: Branch.io

Before Universal Links, the primary mechanism to open up an app when it was installed was by trying to redirect to an app’s URI scheme (registered in the app’s PLIST like so) in Safari. This put the routing logic in Safari, but there was no way to check if the app was installed or not. This meant that developers would try to call the URI scheme 100% of the time, in the off chance that the app was installed, then fallback gracefully to the App Store when not by using a timer.

iOS 9 Universal Links were intended to fix this. Instead of opening up Safari first when a link is clicked, iOS will check if a Universal Link has been registered (an AASA (apple-app-site-association) file should be there in the domain which contains the bundle id of the app and the paths the app should open) for the domain associated with the link, then check if the corresponding app is installed. If the app is currently installed, it will be opened. If it’s not, Safari will open and the http(s) link will load.

Functionally, it allows you have a single link that will either open your app or open your mobile site.

How to Set Up Universal Links in iOS?

Source: Branch.io

1. Configure your app to register approved domains

  • Register your app at developer.apple.com.
  • Enable ‘Associated Domains’ on your app identifier.
  • Enable ‘Associated Domain’ on in your Xcode project

The error “Add the associated Domains feature to your App ID” will go away once you enable the Associated Domains in your APP ID in developer.apple.com as in the previous step. If it doesn’t go away, quit and relaunch the xcode few times and it will work 😖😖😖.

  • Add the proper domain entitlement and make sure the entitlements file is included at build: Xcode will do it automatically by itself.

2. Configure your website to host the ‘apple-app-site-association’ file

Source: Branch.io

What Is An AASA (apple-app-site-association) File?

The AASA (short for apple-app-site-association) is a file that lives on your website and associates your website domain with your native app. In other words, it’s a safe way to prove domain ownership to iOS. With URI schemes, which were the standard way for opening apps on iOS until iOS 9, app developers could register any URI scheme of their liking and iOS, without any verification, would respond to those URI schemes by opening apps. For example, if an indie developer registers the fb:// URI scheme for a test app, there was nothing to stop that, even thoughfb:// is used by the Facebook native app. The AASA file makes Universal Links unique and secure because there is no way for an indie developer to host an AASA file on the facebook.com domain.

Let’s look at some basics of the apple-app-site-association file that will help you in building and hosting one on your domain.

The AASA file contains a JSON object with a list of apps and the URL paths on the domain that should be included or excluded as Universal Links. Here is a sample AASA file:

  • appID: Built by combining your app’s Team ID (goto https://developer.apple.com/account/#/membership/ to get the teamID) and the Bundle Identifier. In the example above, JHGFJHHYX is the Team ID and com.facebook.ios is the Bundle ID.
  • paths: Array of strings that specify which paths are included or excluded from association. You can use NOT (before the path — as in the example JSON above) to disable paths. In this case, all the links on this path will go to the web instead of opening the app. You can use * as a wildcard to enable all paths in a directory (apple doc says: Use * to specify your entire website) and ? to match a single character (/archives/201?/ example in the sample JSON). Please note that these strings are case sensitive and that query strings and fragment identifiers are ignored.

Hosting the AASA File on Your Domain

Have a look at apple docs link below:

Once you are ready with your AASA file, you can now host it on your domain either at https://<<yourdomain>>/apple-app-site-association or at https://<<yourdomain>>/.well-known/apple-app-site-association.

Upload the apple-app-site-association file to your HTTPS web server. You can place the file at the root of your server or in the .well-known subdirectory.

Important: iOS will only attempt to fetch the AASA file over a secure connection (HTTPS).

So, while hosting the AASA file, please ensure that the AASA file:

  • Is served over HTTPS.
  • Uses application/json MIME type.
  • Don’t append .json to the apple-app-site-association filename.
  • Has a size not exceeding 128 Kb (requirement in iOS 9.3.1 onwards).

Supporting Multiple Apps on the Same Domain

You can support multiple apps on the same domain. To do that, you’d need to add a new appID, path dictionary to the details array in the AASA file to look something like this:

If two or more apps associate with the same content path on the website then the order of the appID, paths dictionary in the details array will determine which app will get precedence.

APPLE APP SITE ASSOCIATION (AASA) VALIDATOR

Goto the following link and enter your domain name. It will check if AASA file is valid and is accessible .

Link: https://branch.io/resources/aasa-validator/#resultsbox

Next, you need to handle universal links in your app.

Preparing Your App to Handle Universal Links

When a user taps a universal link, iOS launches your app and sends it an NSUserActivity object that you can query to find out how your app was launched.

To support universal links in your app, take the following steps:

  • Add an entitlement that specifies the domains your app supports.
  • Update your app delegate to respond appropriately when it receives the NSUserActivity object.

As mentioned above, in Xcode, open the Associated Domains section in the Capabilities tab and add an entry for each domain that your app supports, prefixed with applinks: .

example: applinks:www.mywebsite.com

Apple doc says to limit this list to no more than about 20 to 30 domains.

To match all subdomains of an associated domain, you can specify a wildcard by prefixing *. before the beginning of a specific domain (the period is required). Domain matching is based on the longest substring in the applinks entries. For example, if you specify the entries applinks:*.mywebsite.com and applinks:*.users.mywebsite.com, matching for the domain emily.users.mywebsite.com is performed against the longer *.users.mywebsite.com entry. Note that an entry for *.mywebsite.com does not match mywebsite.com because of the period after the asterisk. To enable matching for both *.mywebsite.com and mywebsite.com, you need to provide a separate applinks entry for each.

After you do all these steps perfectly, when you click a universal link, the app will open up and the method application:continueUserActivity:restorationHandler will get called in Appdelegate.

When iOS launches your app after a user taps a universal link, you 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 webpage URL property always contains an HTTP or HTTPS URL, and you can use NSURLComponents APIs to manipulate the components of the URL.

func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([Any]?) -> Void) -> Bool {
print("Continue User Activity called: ")
if userActivity.activityType == NSUserActivityTypeBrowsingWeb {
let url = userActivity.webpageURL!
print(url.absoluteString)
//handle url and open whatever page you want to open.
}
return true
}
Manipulating the components of the URL

For getting the url parameters, use the following function:

//playground code..
var str = “https://google.com/contents/someotherpath?category=series&contentid=1562167825"
let url = URL(string: str)
func queryParameters(from url: URL) -> [String: String] {
let urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false)
var queryParams = [String: String]()
for queryItem: URLQueryItem in (urlComponents?.queryItems)! {
if queryItem.value == nil {
continue
}
queryParams[queryItem.name] = queryItem.value
}
return queryParams
}
// print the url parameters dictionary
print(queryParameters(from: url!))

It will print:

[“category”: “series”, “contentid”: “1562167825”]

Also if you want to check if the app had opened by clicking a universal link or not in the didFinishLaunchingWithOptions method:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool {
var isUniversalLinkClick: Bool = false
if launchOptions[UIApplicationLaunchOptionsUserActivityDictionaryKey] {
let activityDictionary = launchOptions[UIApplicationLaunchOptionsUserActivityDictionaryKey] as? [AnyHashable: Any] ?? [AnyHashable: Any]()
let activity = activityDictionary[“UIApplicationLaunchOptionsUserActivityKey”] as? NSUserActivity ?? NSUserActivity()
if activity != nil {
isUniversalLinkClick = true
}
}
if isUniversalLinkClick {
// app opened via clicking a universal link.
} else {
// set the initial viewcontroller
}
return true
}

Keep in mind:

  • 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.
  • 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.
  • It’s important to understand that if your app uses openURL: to open a universal link to your website, the link does not open in your app. In this scenario, iOS recognizes that the call originates from your app and therefore should not be handled as a universal link by your app.

Testing:

Add the universal link to an email and send it to the testing device. Or you can save it in the notes app. This is what I did for testing.

Here is a youtube video about Universal links:

Issues:

It was a head ace implementing this in my app. I faced so many issues to make it work. So keep in mind the following when implementing and testing Universal linking.

  • Pasting a Universal Link directly into the Safari URL field doesn’t cause the app to open automatically. If you do this, you will have to manually pull the website down so that a prompt will appear at the top asking you to open the respective app.
  • But, if you paste links in Facebook(app), Twitter(app), Mail(app) or even if you go to Facebook on Safari and then click on a universal link, the app opens directly.
  • Universal links will not works for all the apps in iOS. If you click on a universal link from any of the “BLACK LISTED” apps, it will not open the app. Go to this link to know more.
  • As in step one, for the first time you will have to manually pull down the website and click “open” to open the link using the respective app. The iOS will “remember” to open the app instead of opening the safari if the universal link with the registered domains are clicked.
Here is a good article that you can go through to understand the issues with universal links: Link

Enjoy!!

If you enjoyed reading this post, please share and give some clapps so others can find it 👏👏👏👏👏 !!!!

You can follow me on Medium for fresh articles. Also, connect with me on LinkedIn.

If you have any comment, question, or recommendation, feel free to post them in the comment section below!