adjust-icon

Set up direct deep linking

When a user with your app installed clicks an Adjust link, direct deep linking ensures they’re taken directly to specific content within the app.

Setup

iOS provides several methods for receiving direct deep links depending on your app’s implementation. Within these methods, you’ll pass the deep link to the Adjust SDK using one of the following methods:

Use the processAndResolve(_:withCompletionHandler:) method, which does the following:

  • Records attribution from deep link clicks
  • Resolves short branded links to their long branded link equivalent
  • Passes through all other links as is

Your app can then handle the resolved link by parsing it and navigating to the appropriate screen. You can use this method for all deep links, including Adjust long branded links, other universal links, and app scheme deep links.

Method signature
+ (void)processAndResolveDeeplink:(nonnull ADJDeeplink *)deeplink
withCompletionHandler:(nonnull ADJResolvedDeeplinkBlock)completion;

The Adjust SDK has the method, processDeeplink(_:) which can record attribution from deep link clicks if you’re not using short branded links.

Method signature
+ (void)processDeeplink:(ADJDeeplink *)deeplink;

However, the processAndResolve(_:withCompletionHandler:) method supersedes the functionality of this legacy method, so it’s recommended to use this newer method.

Important Implementation Notes

Follow these deep link handling best practices:

  1. Link Handling Requirements

    • Your app should handle Adjust branded links (brandname.go.link) and your universal links (example.com) identically. If your app uses a domain allowlist, ensure your Adjust branded domain is included. For example, both of the following should navigate to the same screen:

      • Adjust branded link: https://brandname.go.link/summer-clothes?promo=beach
      • Universal link: https://example.com/summer-clothes?promo=beach
    • Your app should handle Adjust branded links and app scheme deep links identically. In cases where iOS doesn’t support universal links, Adjust converts them to the equivalent app scheme format. For example, both of the following should navigate to the same screen:

      • Adjust branded link: https://brandname.go.link/summer-clothes?promo=beach
      • App scheme deep link: example://summer-clothes?promo=beach
  2. App State Considerations

    • Ensure your app correctly handles links when they open the app from any state: “not running”, background, or if applicable, foreground.
    • Consider storing deep links if they can’t be handled immediately, such as when the user isn’t yet authenticated, and process them once the app is ready for navigation.
  3. In-App Navigation

    • When an app tries to open its own universal links by calling UIApplication.open(_:options:completionHandler:) or SwiftUI’s openURL, iOS opens them in Safari instead of the app. To use these methods for internal navigation, you can only pass app scheme deep links to them. Alternatively, you can place universal links within your app and use your deep link handling logic to parse them and navigate to the appropriate screen.

Implementation

Use the implementation that aligns with your app’s structure:

UIKit apps using AppDelegate lifecycle

Update your AppDelegate to implement iOS direct deep linking methods.

AppDelegate.swift
// Receive universal link when app is installed
// and link opens app from "not running"
// or background state.
func application(
_ application: UIApplication,
continue userActivity: NSUserActivity,
restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
) -> Bool {
guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
let incomingLink = userActivity.webpageURL
else { return true }
// Create deep link object
guard let deeplink = ADJDeeplink(deeplink: incomingLink) else { return false }
// Send deep link to Adjust's servers for attribution.
// If short branded link, receive long link.
// Otherwise, receive original link.
Adjust.processAndResolve(deeplink) { resolvedLink in
// Handle failure if resolvedLink is nil
guard let resolvedLink = resolvedLink else { return }
// TODO: Handle resolvedLink by parsing its components
// (example: path, query parameters) and
// navigating to the appropriate screen.
}
return true
}
// Receive app scheme deep link when app is installed
// and link opens app from "not running",
// background, or foreground state.
func application(
_ application: UIApplication,
open incomingLink: URL,
options: [UIApplication.OpenURLOptionsKey: Any] = [:]
) -> Bool {
// Create deep link object
guard let deeplink = ADJDeeplink(deeplink: incomingLink) else { return false }
// Send deep link to Adjust's servers for attribution.
// If short branded link, receive long link.
// Otherwise, receive original link.
Adjust.processAndResolve(deeplink) { resolvedLink in
// Handle failure if resolvedLink is nil
guard let resolvedLink = resolvedLink else { return }
// TODO: Handle resolvedLink by parsing its components
// (example: path, query parameters) and
// navigating to the appropriate screen.
}
return true
}

UIKit apps using SceneDelegate lifecycle

Update your SceneDelegate to implement iOS direct deep linking methods.

SceneDelegate.swift
// Receive universal link or app scheme deep link when app is installed
// and link opens app from "not running" state.
func scene(
_ scene: UIScene,
willConnectTo session: UISceneSession,
options connectionOptions: UIScene.ConnectionOptions
) {
// Receive incoming universal link
if let userActivity = connectionOptions.userActivities.first,
userActivity.activityType == NSUserActivityTypeBrowsingWeb,
let incomingLink = userActivity.webpageURL
{
// Create deep link object
guard let deeplink = ADJDeeplink(deeplink: incomingLink) else { return false }
// Send deep link to Adjust's servers for attribution.
// If short branded link, receive long link.
// Otherwise, receive original link.
Adjust.processAndResolve(deeplink) { resolvedLink in
// Handle failure if resolvedLink is nil
guard let resolvedLink = resolvedLink else { return }
// TODO: Handle resolvedLink by parsing its components
// (example: path, query parameters) and
// navigating to the appropriate screen.
}
return
}
// Receive incoming app scheme deep link
guard let incomingLink = connectionOptions.urlContexts.first?.url else { return }
// Create deep link object
guard let deeplink = ADJDeeplink(deeplink: incomingLink) else { return false }
// Send deep link to Adjust's servers for attribution.
// If short branded link, receive long link.
// Otherwise, receive original link.
Adjust.processAndResolve(deeplink) { resolvedLink in
// Handle failure if resolvedLink is nil
guard let resolvedLink = resolvedLink else { return }
// TODO: Handle resolvedLink by parsing its components
// (example: path, query parameters) and
// navigating to the appropriate screen.
}
}
// Receive universal link when app is installed
// and link opens app from background state.
func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
let incomingLink = userActivity.webpageURL
else { return }
// Create deep link object
guard let deeplink = ADJDeeplink(deeplink: incomingLink) else { return false }
// Send deep link to Adjust's servers for attribution.
// If short branded link, receive long link.
// Otherwise, receive original link.
Adjust.processAndResolve(deeplink) { resolvedLink in
// Handle failure if resolvedLink is nil
guard let resolvedLink = resolvedLink else { return }
// TODO: Handle resolvedLink by parsing its components
// (example: path, query parameters) and
// navigating to the appropriate screen.
}
}
// Receive app scheme deep link when app is installed
// and link opens app from background
// or foreground state.
func scene(
_ scene: UIScene,
openURLContexts URLContexts: Set<UIOpenURLContext>
) {
guard let incomingLink = URLContexts.first?.url else { return }
// Create deep link object
guard let deeplink = ADJDeeplink(deeplink: incomingLink) else { return false }
// Send deep link to Adjust's servers for attribution.
// If short branded link, receive long link.
// Otherwise, receive original link.
Adjust.processAndResolve(deeplink) { resolvedLink in
// Handle failure if resolvedLink is nil
guard let resolvedLink = resolvedLink else { return }
// TODO: Handle resolvedLink by parsing its components
// (example: path, query parameters) and
// navigating to the appropriate screen.
}
}

SwiftUI apps using AppDelegate lifecycle

If you haven’t already done so, create an AppDelegate.swift file in your project’s main directory and reference it in your main application file, as shown in the example App.swift file below. This is required to handle app lifecycle events and Adjust SDK integration. Also, implement the onOpenURL SwiftUI modifier, which receives universal links and app scheme deep links when the app is installed.

App.swift
import AdjustSdk
import SwiftUI
@main
struct MyApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
ContentView()
// Receive universal link or app scheme deep link when app is installed
// and link opens app from "not running",
// background, or foreground state.
.onOpenURL { incomingLink in
// Create deep link object
guard let deeplink = ADJDeeplink(deeplink: incomingLink) else { return false }
// Send deep link to Adjust's servers for attribution.
// If short branded link, receive long link.
// Otherwise, receive original link.
Adjust.processAndResolve(deeplink) { resolvedLink in
// Handle failure if resolvedLink is nil
guard let resolvedLink = resolvedLink else { return }
// TODO: Handle resolvedLink by parsing its components
// (example: path, query parameters) and
// navigating to the appropriate screen.
}
}
}
}
}

SwiftUI apps using SceneDelegate lifecycle

Follow the instructions in the SwiftUI apps using AppDelegate lifecycle section. The onOpenURL SwiftUI modifier receives universal links and app scheme deep links when the app is installed and the link opens the app from a background or foreground state.

In addition, implement the scene(_:willConnectTo:options:) method from the UIKit apps using SceneDelegate lifecycle section. This method receives universal links and app scheme deep links when the app is installed and the link opens the app from a “not running” state.