What do the SwiftUI StoreKit views (ProductView, StoreView, SubscriptionStoreView) give you for free, and when do you still build your own paywall?
Predict first
Does a screen using ProductView, StoreView and SubscriptionStoreView with their modifiers compile against the iOS SDK?
import StoreKit
import SwiftUI
struct Shop: View {
@State private var showManage = false
var body: some View {
VStack {
ProductView(id: "com.example.coins.100") { Image(systemName: "bitcoinsign.circle") }
.productViewStyle(.large)
StoreView(ids: ["com.example.coins.100", "com.example.coins.500"])
SubscriptionStoreView(groupID: "21234567") { Text("Go Pro") }
.subscriptionStoreControlStyle(.prominentPicker)
.subscriptionStoreButtonLabel(.multiline)
.storeButton(.visible, for: .restorePurchases)
.manageSubscriptionsSheet(isPresented: $showManage)
}
.onInAppPurchaseCompletion { product, result in
if case .success(.success(let verification)) = result, case .verified(let t) = verification { await t.finish() }
}
.subscriptionStatusTask(for: "21234567") { taskState in
if let statuses = taskState.value { _ = statuses.first?.state }
}
}
}
- Yes, it compiles
- No: SubscriptionStoreView needs an explicit product list
- No: onInAppPurchaseCompletion does not exist
Mental model. The built-in views are a pre-fitted checkout counter that already follows the shop-law signs. You still have to open the door when someone pays (unlock the feature) and can build your own counter only if you will copy the legal signs yourself.
Since iOS 17 StoreKit ships SwiftUI views that handle most of the work. ProductView(id:) shows one product with its icon, name, price and a Buy button, and runs the purchase. StoreView(ids:) shows a list of products. SubscriptionStoreView(groupID:) shows a whole subscription group: the plans, the localised price and renewal terms, the intro-offer wording, a Restore Purchases button, the links to terms and privacy, and the purchase flow, with styles such as .prominentPicker and button label options. They are always correct about prices and terms (they come from the store, localised), which is the part that causes review rejections when hand-built. The check compiles a screen using all three plus .storeButton(.visible, for: .restorePurchases), .manageSubscriptionsSheet(isPresented:), .onInAppPurchaseCompletion and .subscriptionStatusTask.
What they do not do: they do not unlock your features. You still need the store object’s listener and entitlement logic (Q1003), because a purchase completes inside the view and your app must react: onInAppPurchaseCompletion gives you the result so you can finish the transaction and refresh, and subscriptionStatusTask hands you the group’s status so you can hide the paywall for a subscriber. Build your own paywall when design or experiments matter more than the free correctness: a custom layout, a multi-step flow, per-plan copy from your server, or A/B tests of the presentation. Even then, fetch Products and call purchase yourself (Q998), and keep the legal essentials the views include: the real price, the period, auto-renewal wording, Restore, terms and privacy. Test the views with a StoreKit configuration file, since they show nothing useful without products, and check them at large Dynamic Type sizes and in dark mode before shipping.
The views and modifiers exist; compiling proves the API shapes, not how they look at runtime.
Common traps
- Assuming the view unlocks the feature. It only completes the purchase; your code must react.
- Building a custom paywall and dropping Restore or the renewal wording. That is a common reason for rejection.
They might follow up with
- Can I put my own marketing around
SubscriptionStoreView? Yes: it takes a header/marketing content closure and styling modifiers. - Do these views work on older systems? They need iOS 17 (macOS 14); below that you build the screen yourself.
Related questions
- 28.7.2 How do you fetch products and build the catalogue screen: ids, ordering, missing products, period labels and…
- 28.7.4 How do you show a subscriber their status (renewing, in grace, will expire) and let them change or cancel the…
- 28.1.2 What can product.purchase() return or throw, and how should each outcome behave in the UI?
- 28.3.1 How do you compute what a user is entitled to, and which transactions are excluded from…
- 28.5.1 How do you design a paywall that is clear, localised and legal, and how do you run experiments on it?
← How do you fetch products and build the catalogue screen: ids… How do you show a subscriber their status (renewing, in grace, will… →