Skip to the page
Docs
Swift (iOS)

Docs SDKs

Version 0.x

Swift (iOS)

A Swift Package with no dependencies, for iOS 15+ and macOS 12+. SwiftUI and UIKit.

Install

Xcode › File › Add Package Dependencies… › https://github.com/AhmedAshour/apparent-swift (Up to Next Major Version from 0.2.1, product Apparent).

In a Package.swift:

Package.swift
.package(url: "https://github.com/AhmedAshour/apparent-swift", from: "0.2.1"),
// and on your target:
.product(name: "Apparent", package: "apparent-swift"),

Start it

In the App struct's init, or application(_:didFinishLaunchingWithOptions:):

Swift
import Apparent

Apparent.start(app: "<app id>", key: "<write key>")

For development builds that should send nothing: Apparent.start(app: "<app id>", key: "<write key>", disabled: true). You usually don't need it: simulators and TestFlight phones are left out of the numbers by themselves.

Screens

SwiftUI, on each screen's view:

Swift
HomeView().apparentScreen("home")

UIKit: Apparent.trackUIKitScreens() once at launch (a screen per view controller), or Apparent.screen("settings") yourself.

Events and users

Swift
Apparent.track("paywall_view", properties: ["source": "onboarding", "price": 4.99])
Apparent.setProperties(["plan": "free"])        // on every later event; nil removes one
Apparent.identify(userID: user.id)              // after sign-in
Apparent.logout()                               // on sign-out

More in Events and users.

Purchases

With RevenueCat:

Swift
Apparent.linkRevenueCat { Purchases.shared.attribution.setAttributes($0) }

Without RevenueCat, StoreKit 2:

Swift
Apparent.observeStoreKitTransactions()                 // at launch: renewals, refunds, Ask to Buy
Apparent.trackPurchase(try await product.purchase())   // where you sell

Each transaction is sent once, as purchase, subscription_renewed or refund.

Swift
WindowGroup { RootView().apparentDeepLinks() }        // deep_link_opened for links that open the app

// UNUserNotificationCenterDelegate
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse,
                            withCompletionHandler done: @escaping () -> Void) {
    Apparent.handleNotificationResponse(response)     // push_opened
    done()
}
  • deep_link_opened carries the link with secrets in it (token, code, password, key, session… parameters) redacted, and its utm_source, utm_medium and utm_campaign. Handle links yourself? Call Apparent.handleDeepLink(url) there instead of the modifier. A campaign link that opens the app on its first day counts as where that person came from.
  • push_opened carries only the campaign (or utm_campaign) and title you put in the notification's data, never what it said. One per tap.
  • Turn either off with trackDeepLinks: false / trackPushOpens: false.

Errors and opting out

  • Crashes (Swift runtime errors, bad memory access, uncaught exceptions) are saved as the app goes down and sent on the next launch. Other crash reporters keep working.
  • Errors you catch: Apparent.captureError(error).
  • Apparent.optOut() / Apparent.optIn(), remembered across launches.
  • Consent first: optOutByDefault: true collects nothing until Apparent.optIn(), also on later launches until then.
  • Nothing showing up? In a debug build the SDK prints Apparent: app id or key is wrong — events are not being saved once when the app id or key doesn't match. Copy both again from Connect.
  • Simulators and emulators say so themselves (simulator in each batch), so they're never counted, even in a release build.

Privacy

The package ships a PrivacyInfo.xcprivacy: no tracking, no tracking domains. No IDFA, no IDFV, no tracking prompt. The App Privacy answers are on Privacy answers.

Options

Everything else goes in Apparent.start(ApparentConfiguration(app:key:…)):

OptionDefaultDoes
disabledfalseStore and send nothing
userIDnilThe signed-in person, if known at launch
anonymousIDnilAn id you already have (RevenueCat's app user id), asked for on the first launch only
debugfalsePrint what is tracked and sent
sessionTimeout30 minTime in the background before a visit ends
captureErrorstrueReport crashes on the next launch
trackDeepLinkstruedeep_link_opened from .apparentDeepLinks() / handleDeepLink
trackPushOpenstruepush_opened from handleNotificationResponse
optOutByDefaultfalseNothing until optIn()
beforeSendnil{ event in … }: change event.name / event.properties, or return nil to drop it. Sees every event, automatic ones too. If it throws, the event is kept

A start without an app id or a key is ignored, so a later correct one still works.

Updated 2026-10-24 · Markdown · Report a mistake