Skip to content

StoreKit 2 View Modifiers (SwiftUI)

A SwiftUI guide for coding agents. Also covers oninapppurchasecompletion, subscriptionstatustask, currententitlementtask, purchase environment value, dismiss paywall after purchase, observe subscription status.

StoreKit 2 exposes the purchase flow to SwiftUI through view modifiers: hooks for start and completion, tasks that observe entitlements, and a purchase environment value for custom paywalls.

Purchase Lifecycle Hooks

onInAppPurchaseStart and onInAppPurchaseCompletion fire around any purchase started by a StoreKit view in the hierarchy. Completion is the place to dismiss the paywall.

SubscriptionStoreView(groupID: "598392E1")
    .onInAppPurchaseStart { product in
        print(product.displayName)
    }
    .onInAppPurchaseCompletion { product, result in
        dismiss()
    }

Observing Subscription Status

subscriptionStatusTask watches one subscription group and reruns its closure on every change. It delivers an array of statuses, not one value, because family sharing can grant access alongside the user's own purchase.

.subscriptionStatusTask(for: "598392E1") { taskState in
    if let value = taskState.value {
        isPro = !value
            .filter { $0.state != .revoked && $0.state != .expired }
            .isEmpty
    } else {
        isPro = false
    }
}

Entitlements and Products

currentEntitlementTask is the equivalent for consumable and non-consumable purchases. It hands you an optional VerificationResult, optional because the product may never have been bought.

.currentEntitlementTask(for: "1232") { taskState in
    if let verification = taskState.transaction,
       let transaction = try? verification.payloadValue {
        isPro = transaction.revocationDate == nil
    } else {
        isPro = false
    }
}

storeProductTask(for:) loads one product and reruns when it changes, useful for showing a price outside a StoreKit view.

Custom Paywalls

For a hand-built paywall, start purchases through the purchase environment value. It works on all Apple platforms, so the paywall view stays reusable.

@Environment(\.purchase) private var purchase

Button {
    Task {
        if let result = try? await purchase(product) {
            await store.process(result)
            dismiss()
        }
    }
} label: {
    VStack {
        Text(verbatim: product.displayName).font(.headline)
        Text(verbatim: product.displayPrice)
    }
}

Use this guidance in your coding agent

Install the Better Design MCP once. Your agent then loads this page with one call.

get-swiftui-guide({ topic: "swiftui-mastering-storekit2-swiftui-view-modifiers" })
claude mcp add --scope user better-design --transport http https://better-design.com/api/mcp --header "Authorization: Bearer <YOUR_API_KEY>"
Browse related design systems