Purpose
Create and present Encore offers using a fluent, optional-callback API that fits naturally in SwiftUI and UIKit. Supports both per-placement callbacks and global listeners for centralized event handling.
Signature
public static func placement(_ id: String? = nil) -> Placement
public struct Placement {
public func onGranted(_ callback: @escaping (Entitlement) -> Void) -> Placement
public func onNotGranted(_ callback: @escaping (NotGrantedReason) -> Void) -> Placement
public func onLoadingStateChange(_ callback: @escaping (Bool) -> Void) -> Placement
public func show()
public func show() async throws -> EncorePresentationResult
}
Placement Methods
| Method | Parameters | Description |
|---|
placement() | id: String? | Creates a placement with optional identifier. If omitted, auto-generates unique ID |
onGranted() | Entitlement -> Void | Optional. Sets callback invoked when user accepts an offer |
onNotGranted() | NotGrantedReason -> Void | Optional. Sets callback invoked when entitlement not granted (user declined or error) |
onLoadingStateChange() | Bool -> Void | Optional. Sets callback to track loading state |
show() | None | Presents the offer sheet modally (callback-based) |
show() async | None | Presents the offer and returns EncorePresentationResult or throws EncoreError |
Returns / State
placement() returns a Placement instance you can chain with optional callbacks. Call show() to present.
Global Placement Listeners
Global listeners provide centralized event handling for all placement presentations across your app. These are invoked for every placement presentation, making them ideal for analytics, logging, or cross-cutting concerns.
| Method | Parameters | Description |
|---|
Encore.placements.onGranted() | (String, Entitlement) -> Void | Sets global callback invoked whenever any placement grants an entitlement. Receives placement ID and entitlement. Returns PlacementsManager for chaining |
Encore.placements.onNotGranted() | (String, NotGrantedReason) -> Void | Sets global callback invoked whenever any placement does not grant an entitlement. Receives placement ID and reason. Returns PlacementsManager for chaining |
Signature
public static var placements: PlacementsManager { get }
public class PlacementsManager {
public func onGranted(_ callback: @escaping (String, Entitlement) -> Void) -> PlacementsManager
public func onNotGranted(_ callback: @escaping (String, NotGrantedReason) -> Void) -> PlacementsManager
}
Global listeners are invoked in addition to per-placement callbacks. Use global listeners for analytics and centralized logic, while per-placement callbacks handle specific flow control.
Usage Patterns
Global Listeners for Analytics
Set up global listeners once during app initialization to track all placement events:
import Encore
@main
struct YourApp: App {
init() {
Encore.shared.configure(apiKey: "pk_your_api_key")
// Set up global listeners for analytics (chainable)
Encore.placements
.onGranted { placementId, entitlement in
Analytics.track("encore_granted", properties: [
"placement_id": placementId,
"entitlement_type": "\(entitlement)",
"timestamp": Date()
])
}
.onNotGranted { placementId, reason in
Analytics.track("encore_not_granted", properties: [
"placement_id": placementId,
"reason": "\(reason)",
"timestamp": Date()
])
}
}
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
Global listeners receive the placement ID, allowing you to differentiate between different placements in your app (e.g., “cancellation_flow” vs “onboarding_gate”). They are perfect for cross-cutting concerns like analytics, logging, or state synchronization that should happen for every placement presentation.
Cancellation Flow with Named Placement
Button("Cancel Subscription") {
Encore.placement("cancellation_flow")
.onLoadingStateChange { isLoading in
self.showSpinner = isLoading
}
.onGranted { entitlement in
print("User accepted: \(entitlement)")
}
.onNotGranted { reason in
// Proceed with cancellation flow
proceedWithCancellation()
}
.show()
}
Provide a placement ID to identify this specific placement in your global listeners and analytics.
Async/Await Pattern
Button("Cancel Subscription") {
Task {
do {
let result = try await Encore.placement("cancellation_flow").show()
switch result {
case .granted(let entitlement):
print("User accepted: \(entitlement)")
case .notGranted(let reason):
print("Not granted: \(reason)")
proceedWithCancellation()
}
} catch {
// Only system errors are thrown
handleError(error as? EncoreError ?? .unknown)
}
}
}
Passive Offer (No Callbacks)
Button("See Offer") {
// Just show the offer - entitlement state managed automatically via isActivePublisher
Encore.placement("upsell_modal").show()
}
When you don’t need custom flow control, you can omit callbacks entirely. The SDK automatically manages entitlement state through isActivePublisher().