Skip to main content

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.

Overview

Encore.shared.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.

Async-result pattern

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

Basic example

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:
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

public enum PresentationResult {
    case granted(Entitlement)
    case notGranted(NotGrantedReason)
}
See PresentationResult and NotGrantedReason.

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

import Encore
import SwiftUI

@main
struct MyApp: App {
    init() {
        Encore.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 }
            _ = try await product.purchase()
        }

        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

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 for the full rationale.

Re-registration semantics

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 { } wrappingshow() 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 pathtry await show() only throws on transport / SDK errors. The “user dismissed” case is .notGranted(...), not a thrown error.
  • Encore typealiasEncore is a typealias for EncoreClient. Use Encore everywhere; the alias exists for SDK internal reasons (Swift module/type collision workaround).
  • Handler signatureonPurchaseRequest’s closure is async-friendly; you can await directly inside it without a manual Task.

See also