iOS Deep Linking: URL Schemes vs Universal Links

Deep linking in iOS is the practice of leveraging contextual links to drive a user to specific in-app content. For example, if you tap a link to a song on a website, it can open your music app and play that song directly, instead of opening the website in a browser.

Deep linking can improve user experience, engagement, retention and conversion rates for your app. However, it can also be challenging to implement correctly and consistently across different platforms and devices.

Remember to handle the deep link appropriately within your app once it’s opened. You can use custom URL schemes or Universal Links for deep linking, depending on your requirements and the iOS versions you are targeting.

Custom URL Schemes

Apple supports common schemes associated with system apps, such as mailto, tel, sms, and facetime.

mailto:frank@wwdcdemo.example.com
tel:1-408-555-5555
facetime://user@example.com
sms:1-408-555-1212

Native iOS apps and web apps running in Safari on any platform can use these schemes to integrate with system apps and provide a more seamless experience for the user.

You can define your own custom scheme and register your app to support it. This way, other apps or websites can launch your app with specific context data by using specially formatted URLs.

To support a custom URL scheme:

  1. Define the format for your app’s URLs.
myphotoapp:albumname?name="albumname"
myphotoapp:albumname?index=1
  1. Register your scheme so that the system directs appropriate URLs to your app. Register your scheme in Xcode from the Info tab of your project settings. Update the URL Types section to declare all of the URL schemes your app supports.

  2. Handle the URLs that your app receives in AppDelegate.

func application(
  _ application: UIApplication,
  open url: URL,
  options: [UIApplicationOpenURLOptionsKey: Any] = [:]
) -> Bool {

  // Determine who sent the URL.
  let sendingAppID = options[.sourceApplication]
  print("source application = \(sendingAppID ?? "Unknown")")

  // Process the URL.
  guard let components = NSURLComponents(url: url, resolvingAgainstBaseURL: true),
    let albumPath = components.path,
    let params = components.queryItems
  else {
    print("Invalid URL or album path missing")
    return false
  }

  // ...
}

Universal Links

Universal Links are supported on iOS 9 and later versions. They allow apps to claim ownership of specific web domains and open specific content within the app when a user taps on a link to that domain.

These are standard HTTP or HTTPS links that can open your app if it is installed, or fall back to your website if it is not. Universal links are more secure and user-friendly than custom URL schemes, and they also support link attribution and measurement.

https://myphotoapp.example.com/albums?albumname=vacation&index=1
https://myphotoapp.example.com/albums?albumname=wedding&index=17

To support universal links in your app:

  1. Setup associated domains to create a two-way association between your app and your website and specify the URLs that your app handles.

Add a JSON file named apple-app-site-association (without an extension) to your website in .well-known directory and must be served over HTTPS. The file’s URL should match the following format: https://your-domain/.well-known/apple-app-site-association.

{
  "applinks": {
    "details": [
      {
        "appIDs": ["TeamID.BundleID"],
        "components": ["/path1/*", "/path2/deep-link"]
      }
    ]
  }
}

Remember to update the example file with the appropriate App IDs, paths, and your own domain. Only you can store this file on your server, securing the association of your website and your app.

  1. Update your app delegate to respond when it receives an NSUserActivity object with the activityType set to NSUserActivityTypeBrowsingWeb.
func application(
  _ application: UIApplication,
  continue userActivity: NSUserActivity,
  restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
) -> Bool {
  // Get URL components from the incoming user activity.
  guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
    let incomingURL = userActivity.webpageURL,
    let components = NSURLComponents(url: incomingURL, resolvingAgainstBaseURL: true)
  else {
    return false
  }

  // Check for specific URL components that you need.
  // ...
}

Some of the benefits of using universal links are:

  • They are more secure and reliable than custom URL schemes, as they use standard web technologies and prevent URL hijacking.
  • They support link attribution and measurement, as they can pass data parameters to your app and track user engagement.
  • They work across different platforms and devices, such as iOS, Android, and desktop browsers.
  • They enable continuity features such as Handoff, Shared Web Credentials, and Search.

While custom URL schemes are an acceptable form of deep linking, universal links are strongly recommended.

Open Deep Links in iOS

Communicating between apps using deep links means that one app can launch another app and pass data to it using specially formatted URLs.

  1. Using open(_:options:completionHandler:) in UIKit
if let appURL = URL(string: "https://myphotoapp.example.com/albums?albumname=vacation&index=1") {
  UIApplication.shared.open(appURL) { success in
    if success {
      print("The URL was delivered successfully.")
    } else {
      print("The URL failed to open.")
    }
  }
} else {
  print("Invalid URL specified.")
}
  1. Using openURL action in SwiftUI
struct OpenURLExample: View {
  @Environment(\.openURL) private var openURL

  var body: some View {
    Button {
      if let url = URL(string: "https://www.example.com") {
        openURL(url)
      }
    } label: {
      Label("Get Help", systemImage: "person.fill.questionmark")
    }
  }
}

Handle Deep Links in iOS

  1. In SwiftUI, you can handle deep links using the onOpenURL modifier. This modifier allows you to specify a closure that will be executed when your app is launched or resumed with a deep link URL.
@main
struct YourApp: App {
  var body: some Scene {
    WindowGroup {
      ContentView()
        .onOpenURL { url in
          // Handle the deep link URL
          handleDeepLink(url)
        }
    }
  }

  func handleDeepLink(_ url: URL) {
    // Handle the deep link URL
    // You can extract any necessary information from the URL 
    // and perform the appropriate actions in your app
    print("Deep link URL: \(url)")

    // Example: Handle a specific deep link scheme and path
    if url.scheme == "your-deep-link-url-scheme" && url.path == "/your-path" {
      // Perform actions specific to this deep link
      // ...
    }
  }
}
  1. In UIKit with UISceneDelegate

If your app isn’t running, the system delivers the URL to the scene(_:willConnectTo:options:) delegate method after launch.

func scene(
  _ scene: UIScene, willConnectTo session: UISceneSession,
  options connectionOptions: UIScene.ConnectionOptions
) {

  // Get URL components from the incoming user activity.
  guard let userActivity = connectionOptions.userActivities.first,
    userActivity.activityType == NSUserActivityTypeBrowsingWeb,
    let incomingURL = userActivity.webpageURL,
    let components = NSURLComponents(url: incomingURL, resolvingAgainstBaseURL: true)
  else {
    return
  }

  // Check for specific URL components that you need.
  // ...
}

Implement the scene(_:openURLContexts:) method, which is called when your app is already running and a deep link URL is being opened.

func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
  for context in URLContexts {
    let url = context.url
    // handleDeepLink(url)
  }
}