iOS Deep Linking: URL Schemes vs Universal Links

Nov 30, 2024#ios

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

In the context of iOS deep linking, both URL schemes and URI schemes are technically the same. The term “URI scheme” is a broader term encompassing various naming conventions used to identify resources on a network, while “URL scheme” specifically refers to the part of a Uniform Resource Locator (URL) that specifies the protocol used to access the resource.

However, in the context of iOS development, “URL scheme” is the commonly used term when referring to deep linking. It defines a custom protocol used to launch your app and potentially navigate to specific content within the app.

  1. First you define the format for your app’s URLs:
myphotoapp:albumname?name="albumname"
myphotoapp:albumname?index=1
  1. Then you 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. And finally you 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)
  }
}