How to Implement iOS Deep Linking Using Universal Links: Step-by-Step Deep Dive Guide — 2024

Soner Karaevli
6 min readFeb 15, 2024

--

Deep linking is a method of directing users straight to a specific content or page through a hyperlink. This allows users to easily access the particular content they are looking for within an app.

Universal Link vs. URL Scheme: Simplified Comparison

1. Universal Link: ✅

  • Newer Style: Universal Links are a more modern way of handling links.
  • Smooth Experience: They work seamlessly, making it easy for users to move from web browsers to apps.
  • Direct Access: Lets users go straight to specific content in the app.
  • No Browser Detour: If the app is there, it opens directly, skipping the need for a web browser.
  • SSL is necessary.

2. URL Scheme: ❌

  • Older Way: URL Schemes are an older method for links.
  • Need the App: Can cause issues if the app isn’t already installed.
  • Basic Handling: Does basic link stuff, might not be as smooth for users.
  • SSL is not necessary.

In short, Universal Links are the new cool, providing a smoother user experience, especially when moving around different apps, while URL Schemes are a bit older and simpler, often found in older apps.

So let’s start!🔥

Step 1 — Creation of the Apple App Site Association (AASA) File ✅

AASA file is like a helper for iOS apps. It links your website and app, making links work well. Think of it as a guide telling your app where to go when someone clicks a link on your site. Making and setting up this file is important for smooth teamwork between your iOS app and website.

Create a json file like this:

{
"applinks": {
"apps": [],
"details": [
{
"appID": "TeamID.BundleID",
"paths": ["*"]
}
]
}
}
  • the file name must be: apple-app-site-association
  • The presence of [“*”] in the path section means it triggers all paths. If you use [“/users/*”], it will trigger all paths under the ‘users’ path.
  • You should adjust the TeamID and BundleID sections according to your own project.

You can find your Apple TeamID here.

Also you can find your app BundleID in XCode like this:

We made our AASA file. Now, put this JSON file in the main or /.well-known directory of the domain where you want the URL to work. Apple lets us choose where to add it.

Example:

https://www.example.com/apple-app-site-association

or

https://www.example.com/.well-known/apple-app-site-association

Important ⚠️ :

After adding the Apple App Site Association (AASA) file to the .well-known folder on your server, you need to make sure your server is set up correctly. If you’re using a Linux server, make changes in the .htaccess file. For a Windows Server, adjust the mime type section. In short, on the server where you use the AASA file extension, you should set it as .json. After completed, when you open the file in a browser after these changes, it should look like this:

https://www.youtube.com/.well-known/apple-app-site-association

https://twitter.com/.well-known/apple-app-site-association

Let’s check ✅ :

You can check and validate your AASA file on your server with this validator:

https://branch.io/resources/aasa-validator/

Bonus: 🔥🔥🔥

If you want to quickly set up your server for testing using Firebase, you can read my other article here to create it swiftly.

Step 2— Enable Associated Domains On Apple Developer Account ✅

⚠️ For this step, you need to have an enrolled Apple Developer account. ⚠️

  • Then select the Identifier for which we want to add Universal Link and enable the Associated Domains.
  • After all, go to Profiles section and create new Profile and import to xcode. This way, our processes on the Apple Developer side will be completed.

Step 3— Apply the Universal link in XCode ✅

  • Open the Xcode
  • Then go to Signing & Capabilities and click “+ Capability” button then click Associated Domains.
  • Go to the Associated Domains section and add the URLs you want your application to be triggered by. Don’t forget to prefix each domain with “applinks:”.

Attention ❗❗

  • According to Apple’s documentation, they cache the AASA JSON file you added to your server on their own CDNs. When your app is installed, devices download the file from the CDN immediately.
  • Therefore, if you are using a private server that Apple cannot reach, it won’t be able to cache your AASA file. For this reason, when adding domains in XCode, it is better to add them in developer mode for you during the development stage.
  • For development, edit the domain with developer mode by adding “?mode=developer” at the end of your domain string. Like this:
  • If you are using a private server and using a VPN for this, remember that you must run your application on the simulator or a real device with access to the public internet.

❗Important❗

  • When releasing your app, Apple recommends removing the developer mode string from the domain.

Final Part — Triggering the Initial Screen with AppDelegate ✅

  • Add this method on your Appdelegate:
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
let incomingURL = userActivity.webpageURL,
let components = NSURLComponents(url: incomingURL, resolvingAgainstBaseURL: true) else {
return false
}

guard let path = components.path,
let queryItems = components.queryItems else {
return false
}

print("Path: \(path)")

if path == "/changePassword" {

if let albumName = queryItems.first(where: { $0.name == "albumname" })?.value,
let photoIndex = queryItems.first(where: { $0.name == "index" })?.value {

let changePasswordVC = ChangePasswordViewController(albumName: albumName, photoIndex: photoIndex)
let rootViewController = UINavigationController(rootViewController: changePasswordVC)

window?.rootViewController = rootViewController
window?.makeKeyAndVisible()

return true
}
}

return false
}
  • “You can retrieve parameters from the URL and redirect to any desired page.”

If you are using UIScene you should add like this:

func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
let incomingURL = userActivity.webpageURL,
let components = NSURLComponents(url: incomingURL, resolvingAgainstBaseURL: true),
let path = components.path,
let queryItems = components.queryItems else {
return
}

print("Path: \(path)")

if path == "/changePassword" {
if let albumName = queryItems.first(where: { $0.name == "albumname" })?.value,
let photoIndex = queryItems.first(where: { $0.name == "index" })?.value {

let changePasswordVC = ChangePasswordViewController(albumName: albumName, photoIndex: photoIndex)
let rootViewController = UINavigationController(rootViewController: changePasswordVC)

if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
window.rootViewController = rootViewController
self.window = window
window.makeKeyAndVisible()
}
}
}
}

“I hope what I wrote has been helpful. I would be happy if you follow and like. Thank you!🖤

Follow me on github: https://github.com/sonerxdev

References:

--

--