Skip to main content

iOS SDK

Native iOS SDK for deep linking, mobile attribution, and conversion tracking.

Faster setup with AI

Already using Claude Code, Cursor, or Claude Desktop? The LinkForty MCP server can scaffold your SDK integration automatically. Just ask: "Help me add the LinkForty SDK to my iOS app."

Features

  • Deferred deep linking -- route new users to specific content after install
  • Direct deep linking -- handle Universal Links and custom URL schemes
  • Install attribution -- probabilistic fingerprint matching
  • Event tracking -- log in-app events tied to attribution data
  • Revenue tracking -- structured purchase tracking with dedicated method
  • Offline event queue -- events persist across app restarts and retry automatically
  • Programmatic link creation -- create short links from your app
  • Zero external dependencies
  • Privacy-first -- no IDFA collection, includes Privacy Manifest

Requirements

  • iOS 16.0+
  • Xcode 15.0+
  • Swift 5.9+

Installation

In Xcode: File > Add Package Dependencies, then enter:

https://github.com/LinkForty/mobile-sdk-ios.git

Or add to your Package.swift:

dependencies: [
.package(url: "https://github.com/LinkForty/mobile-sdk-ios.git", from: "1.4.0")
]

CocoaPods

pod 'LinkFortySDK', '~> 1.0'

Carthage

github "LinkForty/mobile-sdk-ios" ~> 1.0

Quick Start

Initialize the SDK

Initialize once at app launch, typically in your AppDelegate or @main App struct:

import LinkFortySDK

@main
struct MyApp: App {
init() {
Task {
let config = LinkFortyConfig(
baseURL: URL(string: "https://go.yourdomain.com")!,
apiKey: "your-api-key", // optional -- required for link creation
appToken: "at_your_app_token", // recommended for Cloud -- enables organic-install attribution
debug: true,
attributionWindowHours: 168 // 7 days (default)
)
try await LinkForty.shared.initialize(config: config)
}
}

var body: some Scene {
WindowGroup {
ContentView()
}
}
}
LinkForty.shared.onDeferredDeepLink { deepLinkData in
if let data = deepLinkData {
print("Attributed install: \(data.shortCode)")
// Navigate to content based on custom parameters
if let productId = data.customParameters?["productId"] {
navigateToProduct(productId)
}
} else {
print("Organic install")
}
}

Pass incoming URLs to the SDK from your AppDelegate or SwiftUI onOpenURL:

// SwiftUI
.onOpenURL { url in
LinkForty.shared.handleDeepLink(url: url)
}

// UIKit AppDelegate
func application(_ app: UIApplication,
open url: URL,
options: [UIApplication.OpenURLOptionsKey: Any]) -> Bool {
LinkForty.shared.handleDeepLink(url: url)
return true
}

Register a callback to receive the parsed data:

LinkForty.shared.onDeepLink { url, deepLinkData in
print("Deep link opened: \(url)")
if let data = deepLinkData {
if let route = data.customParameters?["route"] {
navigateToRoute(route)
}
}
}

Track Events

// Custom event
try await LinkForty.shared.trackEvent(
name: "add_to_cart",
properties: ["productId": "123", "category": "electronics"]
)

// Revenue event
try await LinkForty.shared.trackRevenue(
amount: 29.99,
currency: "USD",
properties: ["productId": "123"]
)

Every event is automatically credited to the deep link that most recently opened the app (last-click attribution), along with an app-open session — so analytics can show what users do after clicking a link. Events with no preceding deep-link open are reported as organic. No extra code is required.

Track Screen Views

Reporting screen views lets analytics build a per-link screen-flow funnel — which screens users reach after opening a deep link. Each screen_view carries the same last-click attribution as your other events.

SwiftUI — add the .linkfortyScreen(_:) modifier to a screen:

ProductView()
.linkfortyScreen("ProductDetail")

UIKit — call from viewDidAppear:

override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
Task { try? await LinkForty.shared.trackScreenView(name: "ProductDetail") }
}

You mark the screens you care about (there is no navigation swizzling), which keeps screen names meaningful; the SDK records the previous screen automatically so transitions appear in the funnel.

let result = try await LinkForty.shared.createLink(options: CreateLinkOptions(
deepLinkParameters: ["route": "PRODUCT", "id": "123"],
title: "Check out this product",
utmParameters: UTMParameters(source: "app", medium: "share")
))

print(result.url) // https://go.yourdomain.com/abc123
print(result.shortCode) // abc123
warning

Link creation requires an API key. The simplified endpoint (/api/sdk/v1/links) is only available on LinkForty Cloud, not self-hosted Core.


  1. In Xcode, enable the Associated Domains capability
  2. Add: applinks:go.yourdomain.com
  3. Your LinkForty server automatically hosts the Apple App Site Association file at /.well-known/apple-app-site-association

Example AASA format (served automatically by LinkForty):

{
"applinks": {
"apps": [],
"details": [{
"appID": "TEAM_ID.com.yourcompany.yourapp",
"paths": ["*"]
}]
}
}

API Reference

Initialization

MethodDescription
initialize(config:)Initialize the SDK with configuration

Deep Linking

MethodDescription
onDeferredDeepLink(_:)Register callback for install attribution
onDeepLink(_:)Register callback for direct deep links
handleDeepLink(url:)Pass an incoming URL to the SDK

Event Tracking

MethodDescription
trackEvent(name:properties:)Track a custom in-app event
trackRevenue(amount:currency:properties:)Track a revenue event
trackScreenView(name:properties:)Report a screen view (for screen-flow funnels)

SwiftUI apps can use the .linkfortyScreen("Name") view modifier instead of calling trackScreenView manually.

MethodDescription
createLink(options:)Create a short link programmatically

User Identity

MethodDescription
setExternalUserId(_:)Set external user ID for share attribution (pass nil to clear)
getExternalUserId()Get the current external user ID

Data Access

MethodDescription
getInstallData()Retrieve cached attribution data
getInstallId()Get the server-assigned install UUID

Event Queue

MethodDescription
flushEvents()Manually send all queued events
clearEventQueue()Remove queued events without sending
queuedEventCountNumber of events waiting to be sent

Data Management

MethodDescription
clearData()Wipe all locally stored SDK data
reset()Return SDK to uninitialized state

Offline Resilience

Events are automatically queued when the network is unavailable:

  • Maximum queue size: 100 events
  • Queue persists across app restarts
  • Events are retried automatically when connectivity is restored
  • Use flushEvents() to manually trigger a send
  • Use clearEventQueue() to discard queued events

Privacy

The iOS SDK is designed with privacy in mind:

  • No IDFA collection -- attribution uses probabilistic fingerprinting only
  • Privacy Manifest included -- declares data collection practices for App Store compliance
  • Minimal data collection -- timezone, language, screen resolution, iOS version, app version
  • HTTPS enforced -- all communication uses HTTPS (except localhost for development)

Self-Hosted Configuration

For self-hosted LinkForty Core, omit the API key:

let config = LinkFortyConfig(
baseURL: URL(string: "https://links.yourcompany.com")!,
apiKey: nil,
debug: false
)
try await LinkForty.shared.initialize(config: config)

Troubleshooting

  1. Verify Associated Domains capability is enabled with the correct domain
  2. Check that the AASA file is accessible at https://go.yourdomain.com/.well-known/apple-app-site-association
  3. Long-press the link in Safari -- if "Open in App" appears, Universal Links are configured
  4. Apple caches AASA files; changes can take time to propagate
  1. Check debug logs (debug: true) for install reporting errors
  2. Ensure the attribution window hasn't expired
  3. Fingerprint matching depends on network conditions -- VPNs and privacy relays reduce accuracy

Build errors with SPM

Ensure your project targets iOS 16.0+ and uses Xcode 15+.


Next Steps

Resources