Event

[WWDC2023]Implementing StoreKit functionality with SwiftUI

The content of the presentation is the content of the following sessions.

  1. Meet StoreKit for SwiftUI

If you want to check StoreKit2, please see the following article

wwdc-2021-storekit2-en
[WWDC2021] Check how to implement StoreKit2Based on the StoreKit2 sample app announced at WWDC2021, I will check how to implement the new billing process in StoreKit2....

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:

Implementing StoreKit functionality in SwiftUI
  1. The standard SwiftUI of StoreKit is now used declaratively, making it possible to quickly implement billing functions.
  2. Standard UI, but highly customizable
  3. UI for subscription is also provided
  4. You can check the operation with Preview
  5. 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

  1. StoreView
    • View for displaying the list of charged items
  2. ProductView
    • View used when you want to change the layout by custom for each billing item
  3. 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. ContainerBackgroundPlacement describes the available containers.

https://developer.apple.com/documentation/deviceactivity/deviceactivityreport/containerbackground(_:for:)

.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

  1. Unlocking content after purchase
  2. 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

  1. cancellation
    • cancel for subscription
  2. restorePurchase
  3. redeemCode
    • For entering so-called offer codes
  4. signIn
    • Additional implementation of subscriptionStoreSignInAction Modifier called when button is tapped is required
  5. 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:

  1. We can focus on developing core functionality
  2. A wide variety of Modifiers are also available, making it quite easy to customize
  3. Easy implementation of UIs that follow Apple’s design guidelines
  4. 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

On the other hand, the following disadvantages and points to be noted are likely to occur.

  1. 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.
  2. 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.
  3. 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!

0

COMMENT

Your email address will not be published. Required fields are marked *

CAPTCHA