iOS SDK
Installation
Section titled “Installation”Add the Trace SDK via Swift Package Manager in Xcode:
https://github.com/bmcreations/trace-sdk-iosOr add to your Package.swift:
dependencies: [ .package(url: "https://github.com/bmcreations/trace-sdk-ios", from: "<version>")]Initialization
Section titled “Initialization”Initialize Trace at app launch:
@mainstruct 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.
Compose Multiplatform (KMP)
Section titled “Compose Multiplatform (KMP)”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:
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:
import SwiftUIimport YourKmpFramework
@mainstruct 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.
Handling deep links
Section titled “Handling deep links”Basic listener
Section titled “Basic listener”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)}Universal Links
Section titled “Universal Links”To handle direct deep links when the app is already installed, implement Universal Link handling:
@mainstruct 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)}SwiftUI deep link routing
Section titled “SwiftUI deep link routing”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 })Attribution listener
Section titled “Attribution listener”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)") }}Event tracking
Section titled “Event tracking”TraceClient.shared.trackEvent( name: "purchase_completed", properties: [ "value": "49.99", "currency": "USD", "product_id": "SKU_123" ])See Event Tracking for best practices.
SKAdNetwork
Section titled “SKAdNetwork”Trace automatically handles SKAdNetwork postbacks on iOS. To update conversion values:
TraceClient.shared.updateConversionValue(42)Privacy opt-out
Section titled “Privacy opt-out”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:
// DisableTraceClient.setEnabled(false)
// Re-enableTraceClient.setEnabled(true)
// Check current stateif TraceClient.isEnabled { /* ... */ }The preference is persisted across app restarts. See Configuration — Privacy opt-out for full details.
Associated Domains
Section titled “Associated Domains”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.
1. Register your Team ID
Section titled “1. Register your Team ID”Tell Trace your Apple Team ID so it can serve the apple-app-site-association file automatically:
trace apps update --ios-team-id "ABCDE12345"2. Add the entitlement
Section titled “2. Add the entitlement”In Xcode, go to your target’s Signing & Capabilities tab, add Associated Domains, and add:
applinks:yourapp.traceclick.ioApple will verify the domain by fetching https://yourapp.traceclick.io/.well-known/apple-app-site-association — Trace handles this automatically.