> For the complete documentation index, see [llms.txt](https://docs.amply.tools/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.amply.tools/developer-guide/ios-integration.md).

# iOS integration

In plain English: this page is a collection of how-tos for the Amply iOS SDK beyond "just get it running." It covers session control, deeplink scheme setup, app lifecycle bridging, and the preflight pattern for asking permissions the right way. A PM can skim the section headings to know which knobs exist; an engineer copies the Swift snippets.

**Use this when** you already have the iOS SDK initialized and you need to wire up a specific flow (deeplinks, lifecycle, permissions). **Don't use this when** you haven't finished the Quickstart yet — start there.

## Before you start

* iOS SDK initialized in `AppDelegate` (see [iOS quickstart](/developer-guide/quickstart-ios.md)).
* A strong reference held to your `Amply` instance and any listener adapters on your own side, so they stay alive for the lifetime of the app.

## Managing the session manually

On iOS the SDK does not auto-track app foreground/background. You call the lifecycle methods yourself. This is different from Android, where lifecycle is automatic.

**Signatures** (from `Amply`):

```swift
func pauseSession()
func resumeSession()
func stopSession()
```

Wire them up in your `AppDelegate`:

```swift
// AppDelegate.swift
import AmplySDK

func applicationDidBecomeActive(_ application: UIApplication) {
    AppDelegate.amply?.resumeSession()
}

func applicationDidEnterBackground(_ application: UIApplication) {
    AppDelegate.amply?.pauseSession()
}

func applicationWillTerminate(_ application: UIApplication) {
    AppDelegate.amply?.stopSession()
}
```

If you use `UISceneDelegate`, bridge the same calls from `sceneDidBecomeActive(_:)` and `sceneDidEnterBackground(_:)` instead.

If you need to distinguish "brief interruption" (incoming call, control center, app switcher preview) from "user left the app," call `pauseSession()` from `applicationWillResignActive(_:)` instead of `applicationDidEnterBackground(_:)`. The trade-off: session timing is more sensitive to transient interruptions, but you capture the moment attention leaves the screen.

### When to pause vs. stop

* `pauseSession()` — app went to background. Session can resume when the user returns.
* `resumeSession()` — app returned to foreground before the session timeout.
* `stopSession()` — force-end the current session (for example, user logs out).

## Registering your deeplink scheme

The SDK will deliver campaign deeplinks to your app, but the scheme still needs to be declared in `Info.plist` so iOS routes URLs to you.

```xml
<!-- Info.plist -->
<key>CFBundleURLTypes</key>
<array>
    <dict>
        <key>CFBundleTypeRole</key>
        <string>Editor</string>
        <key>CFBundleURLName</key>
        <string>com.example.yourapp</string>
        <key>CFBundleURLSchemes</key>
        <array>
            <string>yourapp</string>
        </array>
    </dict>
</array>
```

Use the same scheme when you author deeplink URLs in the Amply dashboard (for example `yourapp://promo/summer`).

## Receiving campaign deeplinks

Register a listener after SDK init. The callback returns a `Bool` — `true` if you handled the URL, `false` to let the system default handler try.

**Signature:**

```swift
func registerDeepLinkListener(listener: DeepLinkListener) -> ListenerToken
// DeepLinkListener.onDeepLink(url: String, info: [String: Any]) -> Bool
```

```swift
// DeepLinkAdapter.swift
import AmplySDK

final class DeepLinkAdapter: NSObject, DeepLinkListener {
    func onDeepLink(url: String, info: [String: Any]) -> Bool {
        guard let parsed = URL(string: url) else { return false }
        if parsed.scheme == "yourapp", parsed.host == "promo" {
            let promoId = parsed.lastPathComponent
            Router.shared.showPromo(id: promoId)
            return true
        }
        return false
    }
}

// AppDelegate.swift
private let deepLinkAdapter = DeepLinkAdapter()
private var deepLinkToken: ListenerToken?
// Keep the adapter retained on the AppDelegate, not as a local — and keep the token with it.
deepLinkToken = amply?.registerDeepLinkListener(listener: deepLinkAdapter)
```

{% hint style="warning" %}
**Store the token.** Registering returns a `ListenerToken`, and that token — not the adapter object — is what detaches the listener later via `removeDeepLinkListener(token:)`.

A listener registered once at launch and kept for the life of the app never needs detaching, so storing the token costs nothing. Anything shorter-lived does need it: a listener left attached after its owner is gone keeps that owner alive and the SDK goes on calling it.
{% endhint %}

## Universal Links

Amply deeplinks are delivered through the SDK's listener regardless of scheme format. If you also want to handle Universal Links (`https://yourapp.com/...`) tapped from outside a campaign, use the standard iOS entry point:

```swift
// AppDelegate.swift
func application(
    _ application: UIApplication,
    continue userActivity: NSUserActivity,
    restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
) -> Bool {
    guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
          let url = userActivity.webpageURL else { return false }
    Router.shared.handle(url: url)
    return true
}
```

Track the hit through Amply if it matters for targeting:

```swift
AppDelegate.amply?.track(
    event: "UniversalLinkOpened",
    properties: ["url": url.absoluteString]
)
```

## Push notification preflight popup

Asking iOS for push permission directly means a single "Allow / Don't Allow" dialog with no second chance. The common pattern is to show a soft-preflight popup via an Amply campaign first, then only call `UNUserNotificationCenter` for users who tap "Sure."

High-level flow:

1. Track an event at a good moment, for example `OnboardingCompleted`.
2. In the Amply dashboard, target a popup campaign at that event.
3. The popup's primary button opens a deeplink like `yourapp://permissions/push`.
4. Your deeplink handler calls `requestAuthorization`.

```swift
// DeepLinkAdapter.swift (extended)
func onDeepLink(url: String, info: [String: Any]) -> Bool {
    guard let parsed = URL(string: url) else { return false }
    if parsed.host == "permissions", parsed.lastPathComponent == "push" {
        UNUserNotificationCenter.current().requestAuthorization(
            options: [.alert, .badge, .sound]
        ) { granted, _ in
            AppDelegate.amply?.track(
                event: granted ? "PushPermissionGranted" : "PushPermissionDenied"
            )
        }
        return true
    }
    return false
}
```

For the full recipe including copy tips and retry cadence, see [Soft push permission](/recipes/soft-push-permission.md).

## Listening to SDK system events

Useful during development to see when config loads, when sessions start, and which campaigns evaluate.

**Signature:**

```swift
func setSystemEventsListener(listener: SystemEventsListener) -> ListenerToken
// SystemEventsListener.onEvent(event: EventInterface)
```

```swift
final class SystemEventsAdapter: NSObject, SystemEventsListener {
    func onEvent(event: EventInterface) {
        print("[Amply] \(event.name) \(event.properties)")
    }
}

private let systemEventsAdapter = SystemEventsAdapter()
private var systemEventsToken: ListenerToken?

systemEventsToken = amply?.setSystemEventsListener(listener: systemEventsAdapter)
```

Hold the adapter on the `AppDelegate` or another long-lived owner, and hold the token alongside it — `clearSystemEventsListener(token:)` takes the token, not the adapter.

## Log level

```swift
amply?.setLogLevel(level: "debug")   // 'none' | 'error' | 'warn' | 'info' | 'debug'
```

Leave it at `warn` or lower in production builds.

## ATT and IDFA

The SDK reads IDFA when App Tracking Transparency has been granted. Prompting for ATT is your app's responsibility:

```swift
import AppTrackingTransparency

ATTrackingManager.requestTrackingAuthorization { _ in
    // SDK will pick up IDFA on the next dataset read.
}
```

Request ATT on a meaningful screen, not immediately on launch — Apple rejects apps that prompt before context is established.

## Related

* [iOS quickstart](/developer-guide/quickstart-ios.md) — initial setup, not covered here.
* [Tracking events](/developer-guide/tracking-events.md) — payload shapes and common patterns.
* [Handling deeplinks](/developer-guide/handling-deeplinks.md) — cross-platform deeplink routing patterns.
* [Soft push permission](/recipes/soft-push-permission.md) — full preflight recipe.
* [SDK reference: iOS](/reference/sdk-ios.md) — full method list.
