Usage
Detour delivers links to your app through the Detour.shared singleton. Call resolveInitialLink once at launch to read the link that opened the app, or the deferred link recorded before the install. Call processLink for every link that arrives while the app is running.
Both return a DetourResult with the matched route, which you navigate to.
Shared config
Section titled “Shared config”Build the config once and pass the same value to every call:
import Detour
let detourConfig = DetourConfig( apiKey: "<YOUR_DETOUR_API_KEY>", appID: "<YOUR_DETOUR_APP_ID>", shouldUseClipboard: true, linkProcessingMode: .all)Resolve the initial link
Section titled “Resolve the initial link”Call resolveInitialLink exactly once during cold start:
- If the app uses
UIScene, call theconnectionOptionsoverload fromscene(_:willConnectTo:options:). - Otherwise, call the
launchOptionsoverload fromapplication(_:didFinishLaunchingWithOptions:).
Do not call both overloads for the same launch. The SDK processes the initial link once per session, so the second call returns an empty result.
SceneDelegate (UIScene apps)
Section titled “SceneDelegate (UIScene apps)”For a scene-based app, UIKit supplies cold-start URLs and user activities in
UIScene.ConnectionOptions:
func scene( _ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { Detour.shared.resolveInitialLink( config: detourConfig, connectionOptions: connectionOptions ) { result in if let link = result.link { // Navigate using link.route / link.pathname / link.params } }}AppDelegate (apps without scenes)
Section titled “AppDelegate (apps without scenes)”func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { Detour.shared.resolveInitialLink(config: detourConfig, launchOptions: launchOptions) { result in if let link = result.link { // Navigate using link.route / link.pathname / link.params } }
return true}For a complete, runnable integration with an AppDelegate and SwiftUI state, see Examples.
Handle runtime links
Section titled “Handle runtime links”Universal Links
Section titled “Universal Links”Verified https:// links arrive as an NSUserActivity. Use the callback for
your app’s lifecycle:
func scene(_ scene: UIScene, continue userActivity: NSUserActivity) { guard userActivity.activityType == NSUserActivityTypeBrowsingWeb, let url = userActivity.webpageURL else { return }
Task { @MainActor in let result = await Detour.shared.processLink(url, config: detourConfig) // handle result.link }}func application( _ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { guard userActivity.activityType == NSUserActivityTypeBrowsingWeb, let url = userActivity.webpageURL else { return false }
Task { @MainActor in let result = await Detour.shared.processLink(url, config: detourConfig) // handle result.link } return true}Custom-scheme links (optional)
Section titled “Custom-scheme links (optional)”Registered links such as myapp://product/42 arrive as a URL. Use the callback
for your app’s lifecycle:
func scene( _ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) { guard let url = URLContexts.first?.url else { return }
Task { @MainActor in let result = await Detour.shared.processLink(url, config: detourConfig) // handle result.link }}func application( _ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool { Task { @MainActor in let result = await Detour.shared.processLink(url, config: detourConfig) // handle result.link } return true}Handle resolved links
Section titled “Handle resolved links”processLink and resolveInitialLink both return a DetourResult:
-
processed -
Link processing finished.
-
link -
The matched link, or
nil.
DetourResult also forwards the link’s fields, so you can read them without unwrapping:
-
route -
Full in-app route, query string included:
/details?utm=1. -
pathname -
Path without the query:
/details. -
params -
Query params, already parsed.
-
linkType -
Where the link came from:
.deferred,.verified, or.scheme. -
linkURL -
The original URL.
Link processing modes
Section titled “Link processing modes”DetourConfig.linkProcessingMode controls which link sources the SDK handles:
| Value | Deferred | Universal | Custom scheme |
|---|---|---|---|
.all (default) | Yes | Yes | Yes |
.webOnly | Yes | Yes | No |
.deferredOnly | Yes | No | No |
Use .deferredOnly when your own routing layer already handles runtime links.
Click limits
Section titled “Click limits”Before it delivers a Universal Link, the SDK checks the click against your plan’s limit. Within the limit, links resolve normally.
The check runs when processLink receives a non-nil config, and on the Universal Link path inside resolveInitialLink.
Over the limit
Section titled “Over the limit”The link is not delivered: result.link is nil, so there is nothing to navigate to. The SDK logs [Detour:CLICK_LIMIT_ERROR] with clicksInPeriod and effectiveLimit, under the subsystem com.swmansion.detour.
Other link types
Section titled “Other link types”This flow applies only to Universal Links.
- Deferred and short links — the same plan limits apply on the backend, but the SDK raises no click-limit error. An over-limit link fails to resolve and
result.linkstaysnil. - Custom scheme links — resolved on device, never limit-checked.
For per-plan limits, pay-as-you-go overage, and how to raise them, see Billing & Payments.
Analytics
Section titled “Analytics”resolveInitialLink mounts analytics. Mount them yourself only if you process links without it:
Detour.shared.mountAnalytics(config: detourConfig)Once mounted, the SDK logs one event by itself: app_open, a retention event sent once per session on the first mount. Universal Link opens are not client events. The backend records them through the click endpoint (see Click limits).
Everything else you log yourself:
DetourAnalytics.logEvent(.addToCart, data: ["sku": "abc"])DetourAnalytics.logEvent("promo_banner_tap", data: ["placement": "home_top"])DetourAnalytics.logRetention("day_7_return")