iOS SDK
Swift, callable from Objective-C. No dependencies. Includes a privacy manifest. Pro plan and up.
Requirements
- iOS 13+.
- Swift or Objective-C.
- An SDK key: in the dashboard, open your app → Settings → SDK keys → New key. It's shown once, so copy it then.
Install
In Xcode, choose File → Add Package Dependencies…, paste the repository address, choose Up to Next Major
Version from 0.3.3, and add the Unolink library to your app target.
https://github.com/unolink-dev/unolink-sdk
Unolink installs through Swift Package Manager, not CocoaPods. If your project also uses CocoaPods for other libraries and pod install fails with an encoding error (Ruby 4, or a locale that isn't UTF-8), run it in UTF-8:
LANG=en_US.UTF-8 pod install
Configure
Call configure once, as early as possible: in your @main App's init(), or in
application(_:didFinishLaunchingWithOptions:).
import Unolink
@main
struct MyApp: App {
init() {
#if DEBUG
Unolink.configure(apiKey: "ulk_…", options: UnolinkOptions(debug: true))
#else
Unolink.configure(apiKey: "ulk_…")
#endif
}
var body: some Scene { WindowGroup { ContentView() } }
}
A compiled library can't see your build settings, so pass debug: true from debug builds yourself, as above. That
marks the install as a test device.
Objective-C:
@import Unolink;
[Unolink configureWithApiKey:@"ulk_…" options:[UNLOptions new]];
Options
| Option | Default | What it does |
|---|---|---|
debug | false | Marks this install as a test device. Dashboards hide test devices unless Include test devices is on. |
logLevel | .warn | How much goes to the Xcode console, prefix [Unolink]: .none, .error, .warn, .info, .debug. |
baseURL | https://api.unolink.dev | The SDK API. Leave it as it is. |
Unolink.configure(apiKey: "ulk_…", options: UnolinkOptions(logLevel: .debug, debug: true))
The key is public: it ships inside your app and can only report data for that one app. See SDK keys.
Funnel events
Add one line at each moment that matters. These become the steps in Funnel and Journeys.
Unolink.onboardingCompleted() // the last onboarding screen is done
Unolink.paywallViewed(placement: "onboarding") // a paywall appeared; placement says which one
Unolink.paywallSkipped(placement: "onboarding") // closed without buying
Unolink.signUp(method: "apple") // an account was created
Unolink.signUpSkipped() // went on as a guest (0.3.2+)
Without RevenueCat, report trials and purchases yourself. The amount is a Decimal, so prices never round:
Unolink.trialStarted(productId: "pro_yearly")
Unolink.purchase(amount: product.price, currency: "USD", productId: product.id)
Unolink.purchaseCancelled(productId: product.id) // closed the purchase sheet
Unolink.purchaseFailed(productId: product.id, reason: "payment_failed")
Your own events take a name and up to 20 properties:
Unolink.track("level_up", properties: ["level": 3])
Objective-C:
[Unolink paywallViewedWithPlacement:@"onboarding" properties:nil];
[Unolink purchaseWithAmount:[NSDecimalNumber decimalNumberWithString:@"49.99"] currency:@"USD" productId:nil properties:nil];
The full list, with the names that reach the dashboard, is in Events.
User ID and RevenueCat
If you use RevenueCat, connect it under your app → Settings → RevenueCat. Then don't call
purchase or trialStarted: RevenueCat sends trials, purchases, renewals, cancellations and refunds itself, and
Unolink ignores app purchase calls so nothing counts twice.
To join those purchases to the install, report RevenueCat's own app user ID. With SDK 0.3.2 or later, one line keeps
it current. Call it once at launch, before or after configure:
Unolink.connectRevenueCat { Purchases.isConfigured ? Purchases.shared.appUserID : nil }
Unolink reads the ID at launch, whenever the app becomes active and before each event, and sends it only when it
changed. After Purchases.logIn and Purchases.logOut, call Unolink.revenueCatUserChanged() to send the new ID at
once. Return nil while RevenueCat isn't configured: reading Purchases.shared before that stops the app.
Without it (or before 0.3.2), call setUserId with RevenueCat's ID after Purchases.configure, after every
Purchases.logIn and Purchases.logOut, and on every launch. Use one or the other, not both:
// after Purchases.configure, logIn and logOut, and on every launch
Unolink.setUserId(Purchases.shared.appUserID)
For a guest, that's RevenueCat's anonymous ID, so a purchase made before signing up joins the install right away.
After logIn, it's the account's ID. Every ID an install has reported counts, so the guest's purchase stays joined.
See Guests and accounts.
Unolink never stores the ID itself, only a one-way hash that's different for each of your apps.
Deep links on iPhone
Deferred deep links are Android only. Apple gives apps no install referrer, so your app can't be told which link was
tapped, and Unolink never fingerprints devices to guess. Unolink.onDeferredDeepLink(_:) exists so code
can be shared across platforms, but on iPhone it isn't called today.
In the dashboard, iPhone installs are matched to the link click they came from, without fingerprinting. The match isn't sent to the app. Turn it off per app in its Settings.
Your links still send iPhone visitors straight to the App Store and count their clicks, and the SDK still counts every install and event.
Other calls
| Call | What it does |
|---|---|
Unolink.installId | The random ID of this install, or nil before configure. It identifies no person or device. |
Unolink.flush() | Sends queued events now. Normally not needed. |
Unolink.setEnabled(false) | Turns tracking off and deletes queued events, for your opt-out setting. true turns it back on. |
How it behaves
- First launch: creates a random install ID in Application Support (excluded from iCloud backup, never the Keychain), so a reinstall or a new phone is a new install.
- Automatic events:
first_open,session_start(on launch, or after 30 minutes in the background) andapp_background. They're free. - Events are stored on the device (up to 1,000) and sent in batches every 15 seconds while the app is active, and when it goes to the background. Offline, they wait and retry.
- It never blocks the main thread and never throws.
Test devices
Configure with debug: true in debug builds, as above. Their data is real but hidden until you turn on Include
test devices in Funnel and Journeys. More in Testing.
Privacy
- No IDFA, no App Tracking Transparency prompt, no fingerprinting.
- The bundled privacy manifest declares product interaction (your events) and purchase history (purchases you report), used for analytics, not linked to identity, and no tracking. Use the same answers on your App Store privacy label.
- A random install ID, app version, OS version, SDK version, phone or tablet, language. Country comes from the network edge. IP addresses are never stored.
- Never put names, emails or other personal data in event properties.
Troubleshooting
- Nothing in the dashboard? Turn on Include test devices: debug builds with
debug: trueare hidden by default. - Still nothing? Configure with
UnolinkOptions(logLevel: .debug, debug: true)and search the Xcode console for[Unolink]. A wrong or revoked key is logged as an error. - Some iPhone installs show no link? Expected: not every iPhone install can be matched to a click, and those are shown on their own row. See Deep links on iPhone.
More in Troubleshooting.