The content of the presentation is the content of the following sessions.
If you want to check StoreKit2, please see the following article
StoreKit is getting more and more updated, and the hurdles to implement the billing function of iOS, which has been complicated until now, are getting lower and lower.
Implement StoreKit UI with SwiftUI
Point of Session
Highlights of the above session are as follows:
- The standard SwiftUI of StoreKit is now used declaratively, making it possible to quickly implement billing functions.
- Standard UI, but highly customizable
- UI for subscription is also provided
- You can check the operation with Preview
- This feature is compatible with all Apple platforms.
As usual, the source code of the sample application has been released again this year, and you can also check the details of the source code introduced in the session there.
sample-backyard-birds
main characters
The views added this time are roughly the following three types
- StoreView
- View for displaying the list of charged items
- ProductView
- View used when you want to change the layout by custom for each billing item
- SubscriptionStoreView
- View that displays subscription menus and buttons
StoreView
In the session, the explanation will start around 5:50~.
As shown below, by passing an array of billing item IDs (String) to StoreKitView, a list displaying the billing item information (display name, details, amount) will be automatically displayed.
StoreView(ids: productIDs)
If you want to display the icon, you can get it by writing
StoreView(ids: productIDs) { product in
BirdFoodProductIcon(productID: product.id)
}
You can get the Product from the ViewBuilder argument of the StoreView
parameter, so use it to create a View that displays an image (BirdFoodProductIcon is just an Image (icon)).
ProductView
If you want to customize the UI for individual billing items, use ProductView (session video/code)
ScrollView {
VStack(spacing: 10) {
if let (birdFood, product) = bestValue {
ProductView(id: product.id) {
BirdFoodProductIcon(birdFood: birdFood, quantity: product.quantity)
.bestBirdFoodValueBadge()
}
.padding(.vertical)
.background(.background.secondary, in: .rect(cornerRadius: 20))
.productViewStyle(.large)
.padding()
// ~~omit~~
}
ForEach(premiumBirdFood) { birdFood in
BirdFoodShopShelf(title: birdFood.name) {
ForEach(birdFood.orderedProducts) { product in
ProductView(id: product.id) {
BirdFoodProductIcon(birdFood: birdFood, quantity: product.quantity)
}
}
}
}
}
.scrollClipDisabled()
}
.contentMargins(.horizontal, 20, for: .scrollContent)
.scrollIndicators(.hidden)
As mentioned above, you can also use background colors and padding, so you can flexibly change the layout.
productViewStyle is for switching the display style of ProductView
①Compact, ②Regular, ③Large can be selected (8:15~)
You can also customize the layout of ProductView by yourself by setting a ProductViewStyle-compliant struct with the productViewStyle Modifier (this is the same image as the buttonStyle Modifier that uses the ButtonStyle of a normal SwiftUI Button. )
ProductView(id: ids.nutritionPelletBox)
.productViewStyle(SpinnerWhenLoadingStyle())
struct SpinnerWhenLoadingStyle: ProductViewStyle {
func makeBody(configuration: Configuration) -> some View {
switch configuration.state {
case .loading:
ProgressView()
.progressViewStyle(.circular)
default:
ProductView(configuration)
}
}
}
The above is for displaying ProgressView
while loading billing item information
struct BackyardBirdsStyle: ProductViewStyle {
func makeBody(configuration: Configuration) -> some View {
switch configuration.state {
case .loading: // Handle loading state here
case .failure(let error): // Handle failure state here
case .unavailable: // Handle unavailabiltity here
case .success(let product):
HStack(spacing: 12) {
configuration.icon
VStack(alignment: .leading, spacing: 10) {
Text(product.displayName)
Button(product.displayPrice) {
configuration.purchase()
}
.bold()
}
}
.backyardBirdsProductBackground()
}
}
}
This is for customizing the UI displayed when loading is finished and information acquisition is successful.
SubscriptionStoreView
Next is the view for the subscription
Subscriptions are more difficult to implement than general billing items, but I think that using this view will greatly reduce the implementation hurdles.
SubscriptionStoreView(groupID: passGroupID) {
PassMarketingContent()
.containerBackground(for: .subscriptionStoreFullHeight) {
SkyBackground()
}
}
.backgroundStyle(.clear)
.subscriptionStorePicketItemBackground(.thinMaterial)
.storeButton(.visible, for: .redeemCode)
As before, just pass the groupID(String) and the subscription menu and purchase button UI will be displayed.
The marketing content displayed at the top of the menu is passed as an argument to ViewBuilder (10:40~)
containerBackground can specify the background UI available from iOS17
This time we specify subscriptionStoreFullHeight so that it applies to the entire height of the SubscriptionStoreView
By the way, regarding the difference from the existing Background, etc., the document states as follows (Will it be easier to specify the background of a specific scene, such as subscription View or Navigation?)
automatically filling an entire parent container.
https://developer.apple.com/documentation/deviceactivity/deviceactivityreport/containerbackground(_:for:)ContainerBackgroundPlacement
describes the available containers.
.backgroundStyle(.clear)
sets the background of the menu UI itself to clear (the one specified in the containerBackground
above becomes the background)
subscriptionStorePicketItemBackground
specifies the background of the View for each option in the menu. In this case, thinMaterial
is specified, so it has a frosted glass design that is often seen in iOS apps.
Also, this View not only controls the UI, but also controls the following
- Unlocking content after purchase
- Deactivate buy button after purchase
And by specifying .upgrade
in visibleRelationships as shown below, it also manages the leads to upgrades to higher subscription items (35:30~)
SubscriptionStoreView(
groupID: passGroupID,
visibleRelationships: .upgrade
)
Various other modifiers
Various modifiers have been added as follows
In terms of session videos, you can check from 14:00~
onInAppPurchaseCompletion
You can get the billing product and its result (Sample code)
.onInAppPurchaseCompletion { product, purchaseResult in
guard case .success(let verificationResult) = purchaseResult,
case .success(_) = verificationResult else {
return
}
showingSubscriptionStore = false
}
The code that handles the retrieved results should look familiar in StoreKit2
onInAppPurchaseStart
This is a Modifier to detect when the billing process has started
.onInAppPurchaseStart { (product: Product) in
self.isPurchasing = true
}
subscriptionStatusTask
You can get the status of your subscription
You can also implement processing such as automatically closing the screen by detecting the completion of subscription purchase
subscriptionStatusTask(for: passGroupID) { taskState in
if let statuses = taskState.value {
passStatus = await BirdBrain.shared.status(for: statuses)
}
}
storeProductsTask
You can get the State when getting billing information
You can display the Loading UI while acquiring (26:44~)
@State var productsState: Product.CollectionTaskState = .loading
var body: some View {
ZStack {
switch productsState {
case .loading:
BirdFoodShopLoadingView()
case .failed(let error):
ContentUnavailableView(/* ... */)
case .success(let products, let unavailableIDs):
if products.isEmpty {
ContentUnavailableView(/* ... */)
}
else {
BirdFoodShop(products: products)
}
}
}
.storeProductsTask(for: productIDs) { state in
self.productsState = state
}
}
storeButton
By specifying ①Visibility and ②StoreButtonKind as follows, you can control the display / non-display of the button of the corresponding function
I am using this modifier for the restore button of the subscription mentioned above
.storeButton(.visible, for: .redeemCode)
In the session, the following five were introduced as ② StoreButtonKind
- cancellation
- cancel for subscription
- restorePurchase
- redeemCode
- For entering so-called offer codes
- signIn
- Additional implementation of
subscriptionStoreSignInAction
Modifier called when button is tapped is required
- Additional implementation of
- policies
- Show Terms of Service and Privacy Policy buttons
Wrap Up
So far, we have confirmed how to implement StoreKit functions with SwiftUI standard Views.
I feel that this feature has the following advantages:
- We can focus on developing core functionality
- A wide variety of Modifiers are also available, making it quite easy to customize
- Easy implementation of UIs that follow Apple’s design guidelines
- When creating the UI, the platform side can handle the complexities.
- Multilingual and multi-regional support
- Especially since money is involved, this is a feature that requires careful implementation and testing.
- accessibility
- Supports multiple platforms
- For example, the subscription button style UI is automatically adjusted for each platform (31:45~)
- Will it work with VisionPro?
- Billing enabled/disabled check by ScreenTime (reference)
- Upcoming SwiftUI-focused feature updates
- Multilingual and multi-regional support
On the other hand, the following disadvantages and points to be noted are likely to occur.
- If you want to realize complicated layouts and functions, it may be unsatisfactory as a function
- However, since it can be customized quite a bit, there seems to be no problem unless you want to make something very elaborate.
- It’s provided as standard, so I don’t really know what’s actually going on inside (Is it like black magic?), but I end up using it for the time being.
- In some cases, it may be necessary to discuss with the designer what kind of layout customization is possible, and how to implement it.
- If necessary, engineers may enter the support from the design stage
However, with the addition of this function, the hurdles for implementing the billing function on the Apple platform have become lower, so if you implement it on iOS17 or later (far away..), use this function to implement it quickly. please look!
This information is based on the information of the session video of WWDC2023, and is a summary of the contents of the beta version. , Please implement it while checking the latest information!