Overview
StoreKit is an Apple framework that supports in-app purchases, ad network attribution, Apple Music integration, and enabling app ratings and reviews.
Resources
- WWDC 2022 video What's new with in-app purchase
- WWDC 2022 video Implement proactive in-app purchase restore
- WWDC 2022 video What's new in StoreKit testing
- WWDC 2023 video Meet StoreKit for SwiftUI
Usage
To use StoreKit in an app:
Select File ... New ... File... or press cmd-n.
Scroll the "Other" category.
Select "StoreKit Configuration File".
A file name can be entered, but the default name of "Configuration.storekit" is fine.
Click the "+" in the lower-left of the editor and select a type. For one-time purchases, select "Add Non-Consumable In-App Purchase".
Enter a "Reference Name". This can be the app name. To find this, click the top entry in the Navigator, select the "General" tab, and note the value of Identity ... Display Name.
Enter a "Product ID". This can be the project bundle identifier. To find this, click the top entry in the Navigator, select the "General" tab, and note the value of Identity ... Bundle Identifier.
Enter a price.
Under "Localizations", double-click an option such as "English (U.S.)".
Enter a "Display Name". This can be the same as the Reference Name entered above.
Enter a "Description" of the purchasable item.
Click "+" under "Localizations" to add more supported locales.
Repeat steps 5-12 for each additional non-consumable that can be purchased.
Create a new Swift file. A good name is
StoreKit.swift
.Add the following in
StoreKitStore.swift
:import StoreKit
class StoreKitStore: NSObject, ObservableObject {
// This is a Set of purchasable product ids.
private var allProductIdentifiers =
Set(["r.mark.volkmann.gmail.com.GiftTrack"])
private var productsRequest: SKProductsRequest?
private var fetchedProducts: [SKProduct] = []
typealias CompletionHandler = ([SKProduct]) -> Void
private var completionHandler: CompletionHandler?
override init() {
super.init()
fetchProducts { products in
print("products =", products)
}
}
private func fetchProducts(
_ completion: @escaping CompletionHandler
) {
guard productsRequest == nil else { return }
completionHandler = completion
productsRequest =
SKProductsRequest(productIdentifiers: allProductIdentifiers)
productsRequest?.delegate = self
productsRequest?.start()
}
}
extension StoreKitStore: SKProductsRequestDelegate {
func productsRequest(
_ request: SKProductsRequest,
didReceive response: SKProductsResponse
) {
let loadedProducts = response.products
let invalidProducts = response.invalidProductIdentifiers
guard !loadedProducts.isEmpty else {
print("failed to load products")
if !invalidProducts.isEmpty {
print("invalid products found: \(invalidProducts)")
}
productsRequest = nil
return
}
// Cache the fetched products.
fetchedProducts = loadedProducts
// Notify listeners of loaded products.
DispatchQueue.main.async {
self.completionHandler?(loadedProducts)
self.completionHandler = nil
self.productsRequest = nil
}
}
}In the
{app-name}.swift
file, add the following inside the struct that inherits fromApp
:@StateObject private var store = StoreKitStore()
In any views that need to access the store, add the following:
@EnvironmentObject private var store: StoreKitStore
In the Navigator, select
{app-name}.swift
.In the top bar, click the project name to the left of the device drop-down and select "Edit Scheme..." which opens a dialog.
Click "Run" in the dialog left nav.
Click the "Options" tab.
Change the value of "StoreKit Configuration" from "None" to "Configuration.storekit".
Click the "Close" button.
Displaying Products
To display products in SwiftUI, get an array of product ids where each is a String
and pass it to the StoreView
view.
StoreView(ids: productIds)
This produces a UI that works on all platforms including iOS, iPadOS, macOS, and watchOS.
This is best tested in the Simulator rather than in Previews.
The productViewStyle
view modifier can be applied to StoreView
to change the size and layout of each product section. It takes one argument which can be .regular
(default), .large
, or .compact
. The .large
option has an issue with horizontal alignment.
Testing from Xcode
Xcode and the Simulator can be used to test in-app purchases without the need to configure them in AppStoreConnect. See the YouTube video How to easily test InApp Purchases in an iOS app with Josh Holtz.
To create a StoreKit configuration file to be used for testing:
- Select File ... New ... File...
- Select "StoreKit Configuration File" and click the "Next" button.
- Enter a name like "Test". The file will have an extension of
.storekit
. - If you have already described the products in AppStoreConnect, check the "Sync this file with an app in App Store Connect" checkbox.
- Click the "Next" button.
- Select the top project directory that contains the main
.swift
file. - Click the "Create" button.
To describe products in the new StoreKit configuration file:
In the special editor that opens for the new StoreKit configuration file, for each product to be offered by the app:
- Click the "+" in the lower-left and select a kind of product to add such as "Add Non-Consumable In-App Purchase".
- Enter a "Reference Name" that describes the product.
- Enter a product ID that is unique among all of your apps. It could be the app bundle ID followed by a product-specific identifier.
- Enter a price.
- Optionally select "On" for "Family Sharing".
- Expand the "Localizations" section.
- For each language to be supported, enter a "Display Name" and "Description" for the product.
To tell the Simulator to use this new .storekit
configuration file:
- Click the dropdown to the left of the device dropdown at the top of the Xcode window.
- Select "Edit Scheme..."
- In the left nav, select "Run".
- Select the "Options" tab.
- Change the value selected in the "StoreKit Configuration" dropdown to the name of the new
.storekit
configuration file. - Click the "Close" button.
To test an in-app purchase:
- Run the app in the Simulator and make an in-app purchase.
- If the products do not appear in the app, try running it again.
- In Xcode, select Debug ... StoreKit ... Manage Transactions... to see a history of all the in-app purchase transactions.
- Right-click a transaction to get a menu with the options "Approve Transaction", "Decline Transaction", "Refund Purchase", "Resolve Issue", "Request Price Increase Consent", and "Delete Transaction".
- For subscriptions, click the "Subscription Options" button below a transaction to get a dialog where the subscription can be cancelled.
Example Project
See StoreKitDemo which is not yet fully functional. StoreKit still seems difficult to correctly configure and use!