iOS SDK
The iOS SDK is currently in beta. The public API surface in Sources/LinkSense/*.swift may change between 0.x releases. Pin to an exact version in production using .exact("0.1.0") in your Package.swift and review the changelog before upgrading. We commit to API stability at 1.0.
Overview
The LinkSense iOS SDK delivers four capabilities:
- Install-time attribution — match a user's post-install session to the link they tapped before downloading your app.
- Deferred deep linking — surface the original link URL via a callback after install.
- Universal Link forwarding — translate Universal Link taps into structured navigation events.
- Link CRUD — create, read, update, and delete dynamic links directly from the device.
The SDK ships as a Swift Package with zero third-party dependencies. It does not collect IDFA and never reads the clipboard without explicit opt-in.
Prerequisites
- iOS 14.0 or later deployment target
- Xcode 15 or later, Swift 5.9+
- An SDK key from your LinkSense project (Project Settings → SDK Keys)
- Universal Links configured for your project subdomain (see iOS Deep Linking)
Installation
Add the SDK via Swift Package Manager. In Xcode, choose File → Add Package Dependencies… and paste the repository URL:
https://github.com/LinkSense-net/linksense-ios-sdk.gitOr add the dependency directly in your Package.swift:
.package(url: "https://github.com/LinkSense-net/linksense-ios-sdk.git", from: "0.1.0")The iOS SDK ships only via Swift Package Manager. There is no podspec. If your project still uses CocoaPods, add the package via Xcode's Add Package Dependencies dialog alongside your existing Podfile.
Configuration
Call configure once at app launch, then start to begin install-time attribution:
import LinkSense LinkSense.shared.configure( LinkSenseConfiguration( sdkKey: "ls_sdk_<project>_<random>", enableClipboard: false, logLevel: .off ))LinkSense.shared.start()Configuration fields:
| Field | Type | Required | Description |
|---|---|---|---|
sdkKey | String | Yes | Your project SDK key (format: ls_sdk_<project>_<random>). Generate one in Project Settings → SDK Keys. |
enableClipboard | Bool | No | Read the pasteboard on first launch for clipboard-based deferred attribution. iOS 14+ shows a paste notification to the user. Default false. |
logLevel | LinkSenseLogLevel | No | Diagnostic logging level (.off, .error, .warning, .info, .debug). Default .off — recommended for production. |
Subsequent calls to configure after the first are no-ops by design. The SDK reuses the same install idempotency key across cold starts to prevent double-attribution.
Handling Deferred Deep Links
Register a handler to receive the original dynamic link URL once attribution resolves. The callback fires on the main queue.
LinkSense.shared.onDeferredDeepLink { url in if let url = url { router.handle(url) }}The handler fires the first time start() resolves an install with a matching click, then never again for that install. If no click matched, the URL is nil.
Forwarding Universal Links
When iOS opens your app via a Universal Link (warm path), forward the NSUserActivity to the SDK so it can resolve the link target:
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { return LinkSense.shared.continueUserActivity(userActivity) { url in if let url = url { router.handle(url) } }}For AASA hosting and the Associated Domains entitlement, see iOS Deep Linking.
Link CRUD
The SDK exposes createLink, link(withId:), updateLink, and deleteLink. All completions dispatch on the main queue.
LinkSense.shared.createLink( options: CreateLinkOptions( name: "Summer sale", desktopUrl: URL(string: "https://example.com/sale"), iosFallbackUrl: URL(string: "https://apps.apple.com/app/id123456789") )) { result in switch result { case .success(let link): print("Created: \(link.url)") case .failure(let error): print("Failed: \(error)") }}UpdateLinkOptions uses double-optionals (URL??) to distinguish three states: omit the field (.none), explicitly clear it (.some(.none)), or set a new value (.some(.some(value))).
Error Handling
All CRUD methods return Result<T, LinkSenseError>. The LinkSenseError enum maps server error codes to typed cases:
LinkSense.shared.link(withId: "ln_abc123") { result in switch result { case .success(let link): // use link break case .failure(let error): switch error { case .notConfigured: print("Call configure() first") case .network(let underlying): print("Network failure: \(underlying)") case .rateLimited(let retryAfter): print("Retry after \(retryAfter)s") case .server(let code, let message, _): print("Server \(code): \(message)") default: print("Other error: \(error)") } }}Common cases: .notConfigured, .network, .unauthorized, .forbiddenCrossScope, .forbiddenField, .invalidInput(fieldErrors:), .notFound, .customPathExists, .rateLimited(retryAfter:), .server(code:message:status:).
Example & Resources
The iOS SDK repository ships with a SwiftUI sample app demonstrating configuration, deferred deep link handling, Universal Link forwarding, and link CRUD.