> ## Documentation Index
> Fetch the complete documentation index at: https://docs.encorekit.com/llms.txt
> Use this file to discover all available pages before exploring further.

# iOS Integration Patterns

> Two ways to handle Encore offer results in Swift — async-result and handler-based — with a decision tree for picking the right one.

## Overview

`try await Encore.shared.placement(_:).show()` returns a `PresentationResult`. You can either branch on the returned value at the call site (async-result) or register global `onPurchaseRequest` / `onPassthrough` handlers (handler-based). Both work; pick whichever fits the call site.

For the cross-platform decision tree, see [Integration Patterns](/concepts/integration-patterns).

***

## Async-result pattern

Use this when the place you call `show()` from already has access to your purchase + decline logic.

### Basic example

```swift theme={null}
import Encore

func cancelTapped() {
    Task {
        do {
            let result = try await Encore.placement("cancel_flow").show()
            switch result {
            case .granted(let entitlement):
                // User accepted — entitlement is already applied by the SDK
                navigateHome()
            case .notGranted(let reason):
                // User declined or no offers — proceed with original flow
                proceedWithCancellation(reason: reason)
            }
            // Code that runs in both branches lives here, written once
            analytics.track("cancel_flow_resolved")
        } catch {
            // Network / SDK error
            proceedWithCancellation(reason: nil)
        }
    }
}
```

### From SwiftUI

`Button` action bodies are synchronous, so wrap in `Task`:

```swift theme={null}
struct CancelView: View {
    var body: some View {
        Button("Cancel Subscription") {
            Task {
                let result = try? await Encore.placement("cancel_flow").show()
                switch result {
                case .granted:
                    dismiss()
                case .notGranted, .none:
                    proceedWithCancellation()
                }
            }
        }
    }
}
```

### Result type

```swift theme={null}
public enum PresentationResult {
    case granted(Entitlement)
    case notGranted(NotGrantedReason)
}
```

See [PresentationResult](../sdk-reference/presentation-result) and [NotGrantedReason](../sdk-reference/not-granted-reason).

***

## Handler pattern

Use this when the `show()` call site is a third-party delegate (Superwall, RevenueCat) or a UI action that doesn't have direct access to your purchase code.

### Register at app launch

```swift theme={null}
import Encore
import SwiftUI

@main
struct MyApp: App {
    init() {
        Encore.shared.configure(apiKey: "pk_live_...")

        Encore.shared.onPurchaseRequest { request in
            // Route to StoreKit / RevenueCat / Adapty / your billing
            guard let product = try await Product.products(for: [request.productId]).first else {
                return false
            }
            let result = try await product.purchase()
            if case .success = result { return true }   // dismisses the offer sheet
            return false
        }

        Encore.shared.onPassthrough { placementId in
            // Resume the user's original flow for this placement
            AppRouter.shared.handlePassthrough(placementId)
        }
    }

    var body: some Scene {
        WindowGroup { ContentView() }
    }
}
```

### Then `show()` from anywhere

```swift theme={null}
Button("Cancel Subscription") {
    Task { try? await Encore.placement("cancel_flow").show() }
}
```

The handlers fire when the placement resolves. The result of `show()` is ignored — control flow lives in the handlers.

***

## When to use which

* **Async-result** — call site already imports your billing client. Branch-specific code lives at the call site.
* **Handler** — Encore is invoked from third-party paywall delegates (Superwall, RevenueCat) or from many UI sites that share post-purchase logic.
* **Mixed** — register a handler for cross-cutting analytics, and still branch on `await show()` for site-specific UI navigation.

See the [decision tree](/concepts/integration-patterns#decision-tree) for the full rationale.

***

## Re-registration semantics

```swift theme={null}
Encore.shared.onPurchaseRequest { _ in /* v1 */ }
Encore.shared.onPurchaseRequest { _ in /* v2 — replaces v1 */ }
```

Each handler setter replaces the previous closure. There is no double-firing. You don't need to call any `removeHandler` API.

***

## Platform-specific notes

* **`Task { }` wrapping** — `show()` is `async throws`; SwiftUI `Button` actions and UIKit `@IBAction` selectors are synchronous, so wrap the call in `Task { ... }`.
* **`PresentationResult` is non-throwing in the success path** — `try await show()` only throws on transport / SDK errors. The "user dismissed" case is `.notGranted(...)`, not a thrown error.
* **`Encore` is the entry point** — `Encore` is a `public final class` conforming to `EncoreProtocol`. Access it via `Encore.shared`. (There is no `EncoreClient` typealias.)
* **Handler signature** — `onPurchaseRequest`'s closure is `(PurchaseRequest) async throws -> Bool`; you can `await` directly inside it without a manual `Task`. Return `true` on a successful purchase so Encore auto-dismisses the offer sheet.

***

## See also

* [Concepts — Integration Patterns](/concepts/integration-patterns) — cross-platform decision tree
* [Quickstart — Present Offers](/publishers/ios/quickstart/present-offers) — handler-pattern walkthrough
* [Using Superwall](./using-superwall) — handler pattern from a Superwall delegate
* [Using RevenueCat](./using-revenuecat) — handler pattern alongside RevenueCat paywalls
* [SDK Reference — `placement()`](../sdk-reference/placement)
* [SDK Reference — `PresentationResult`](../sdk-reference/presentation-result)
