Skip to content

iOS SDK

Add the Trace SDK via Swift Package Manager in Xcode:

https://github.com/bmcreations/trace-sdk-ios

Or add to your Package.swift:

dependencies: [
.package(url: "https://github.com/bmcreations/trace-sdk-ios", from: "<version>")
]

Initialize Trace at app launch:

@main
struct MyApp: App {
init() {
TraceClient.shared.initialize(
config: TraceConfig(
apiKey: "tr_live_xxxxxxxxxxxx",
hashSalt: "your_64_char_hex_salt"
)
)
}
var body: some Scene {
WindowGroup {
ContentView()
}
}
}

The SDK automatically performs attribution on first launch.

If your iOS app uses Kotlin Multiplatform with Compose Multiplatform, you don’t need the Swift SDK. The shared KMP SDK handles everything from commonMain.

Add the SDK to your shared module’s build.gradle.kts:

kotlin {
sourceSets {
commonMain.dependencies {
implementation("io.traceclick:trace-sdk:<version>")
implementation("io.traceclick:trace-sdk-nav3-kmp:<version>") // for Navigation3
}
}
}

Create an iOS entry point in iosMain:

iosMain/kotlin/.../MainViewController.kt
fun MainViewController() =
ComposeUIViewController {
TraceProvider {
AppNavigation() // Your shared Compose UI
}
}
fun initializeTrace() {
Trace.initialize(
config = TraceConfig(
apiKey = "tr_live_xxxxxxxxxxxx",
hashSalt = "your_64_char_hex_salt",
region = Region.US,
),
)
}

Then wrap it in a Swift host:

iOSApp.swift
import SwiftUI
import YourKmpFramework
@main
struct iOSApp: App {
init() {
MainViewControllerKt.initializeTrace()
}
var body: some Scene {
WindowGroup {
ComposeView()
.ignoresSafeArea(.all)
}
}
}

TraceProvider, rememberDeepLinkMapper, and the Nav3 entry decorator all work identically on iOS — the same commonMain code runs on both platforms.

TraceClient.shared.setDeepLinkListener { deepLink in
// deepLink.path — e.g. "/product/123"
// deepLink.params — e.g. ["color": "blue"]
// deepLink.isDeferred — true if delivered via install attribution
navigateTo(deepLink.path)
}

To handle direct deep links when the app is already installed, implement Universal Link handling:

@main
struct MyApp: App {
init() {
TraceClient.shared.initialize(config: /* ... */)
}
var body: some Scene {
WindowGroup {
ContentView()
.onOpenURL { url in
TraceClient.shared.handleUniversalLink(url)
}
}
}
}

For UIKit apps using SceneDelegate:

func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
guard let url = userActivity.webpageURL else { return }
TraceClient.shared.handleUniversalLink(url)
}

Build a route mapper with route() for navigation and action() for side-effects, then attach .onDeepLink() to handle both:

struct ContentView: View {
@State private var path: [AppRoute] = []
private let mapper = TraceRouteMapper<AppRoute> { m in
m.route("/product/{id}") { .product(id: $0.require("id")) }
m.route("/invite/{code}") { .invite(code: $0.require("code")) }
m.action("/claim/promo/{amount}") { claimReward($0.require("amount")) }
}
var body: some View {
NavigationStack(path: $path) {
HomeView()
.navigationDestination(for: AppRoute.self) { route in
switch route {
case .product(let id): ProductView(id: id)
case .invite(let code): InviteView(code: code)
}
}
}
.onDeepLink(path: $path, mapper: mapper)
}
}

The .onDeepLink modifier observes incoming deep links, routes navigation results to the path, and executes action results as side-effects. Pass authGate to defer navigation until the user is authenticated:

.onDeepLink(path: $path, mapper: mapper, authGate: { isLoggedIn })

React to the attribution result for analytics:

TraceClient.shared.setAttributionListener { result in
switch result {
case .attributed(let method, let campaignId, _):
Analytics.track("install_attributed", properties: [
"method": method,
"campaign": campaignId ?? "unknown"
])
case .organic:
Analytics.track("install_organic")
case .error(let message):
print("Attribution failed: \(message)")
}
}
TraceClient.shared.trackEvent(
name: "purchase_completed",
properties: [
"value": "49.99",
"currency": "USD",
"product_id": "SKU_123"
]
)

See Event Tracking for best practices.

Trace automatically handles SKAdNetwork postbacks on iOS. To update conversion values:

TraceClient.shared.updateConversionValue(42)

Respect user consent by toggling data collection at runtime. A common use case is disabling Trace after the user denies App Tracking Transparency (ATT):

import AppTrackingTransparency
ATTrackingManager.requestTrackingAuthorization { status in
if status == .denied || status == .restricted {
TraceClient.setEnabled(false)
}
}

You can also toggle from a settings screen:

// Disable
TraceClient.setEnabled(false)
// Re-enable
TraceClient.setEnabled(true)
// Check current state
if TraceClient.isEnabled { /* ... */ }

The preference is persisted across app restarts. See Configuration — Privacy opt-out for full details.

To enable Universal Links, two things are needed: the Associated Domains entitlement in Xcode, and an Apple App Site Association file on your Trace subdomain.

Tell Trace your Apple Team ID so it can serve the apple-app-site-association file automatically:

Terminal window
trace apps update --ios-team-id "ABCDE12345"

In Xcode, go to your target’s Signing & Capabilities tab, add Associated Domains, and add:

applinks:yourapp.traceclick.io

Apple will verify the domain by fetching https://yourapp.traceclick.io/.well-known/apple-app-site-association — Trace handles this automatically.