了解Swift UI 中 StoreKit 2 新功能

前言

StoreKit 为我们提供了通过应用程序获得收入的机会。它允许我们设置应用内购买和订阅的购买流程。StoreKit 2 引入了一种基于现代 Swift 的 API,用于构建类型安全的应用内购买。下面我们将开始关于 StoreKit 2 的系列文章。

配置项目

首先,我们必须在项目的 "Signing & Capabilities" 选项卡中配置应用内购买项目。接下来,应该创建一个 StoreKit 配置文件,以便在没有与 App Store 的网络连接的情况下测试应用内购买功能。前往 "File -> New -> File" 并选择 "StoreKit Configuration File"。

可以创建一个仅本地的配置文件,并将其填充为测试订阅和应用内购买项目。另一种选择是启用 "Sync this file with an app in App Store Connect" 复选框,从 App Store Connect 获取订阅和应用内购买项目列表。

最后一步是使用预定义的 StoreKit 配置文件运行你的应用程序。需要编辑项目的 scheme,并在运行部分的选项标签中选择的 StoreKit 配置文件。现在,已经拥有一个完全配置的项目,允许我们在 Xcode 中测试应用内购买。

构建支付功能

让我们开始构建我们的支付功能,引入 Store 类型来处理与应用内购买相关的所有逻辑。

swift 复制代码
import StoreKit

@MainActor final class Store: ObservableObject {
    @Published private(set) var products: [Product] = []
    
    init() {}
    
    func fetchProducts() async {
        do {
            products = try await Product.products(
                for: [
                    "123456789", "987654321"
                ]
            )
        } catch {
            products = []
        }
    }
}

正如在上面的示例中所看到的,我们定义了 Store 类型,用于获取和存储将显示在支付屏幕上的产品列表。StoreKit 2 框架提供了 Product 类型,该类型封装了与应用内购买相关的所有逻辑。Product 类型具有一个名为 products 的静态函数,我们可以使用它来通过提供标识符集合来获取产品列表。

swift 复制代码
struct ContentView: View {
    @StateObject private var store = Store()
    
    var body: some View {
        VStack {
            if store.products.isEmpty {
                ProgressView()
            } else {
                ForEach(store.products, id: \.id) { product in
                    Button {
                        Task {
                            try await store.purchase(product)
                        }
                    } label: {
                        VStack {
                            Text(verbatim: product.displayName)
                                .font(.headline)
                            Text(verbatim: product.displayPrice)
                        }
                    }
                    .buttonStyle(.borderedProminent)
                }
            }
        }
        .task {
            await store.fetchProducts()
        }
    }
}

我们使用 Store 类型来获取和显示可用的应用内购买列表。Product 类型的实例包含了我们需要显示的所有信息,如应用内购买的标题、描述和价格。

Product 类型还具有 purchase 函数,我们可以使用它来启动特定产品的应用内购买流程。它返回一个 PurchaseResult 枚举的实例,定义了三种情况:成功、挂起和用户取消。

swift 复制代码
@MainActor final class Store: ObservableObject {
    // ...
    
    @Published private(set) var activeTransactions: Set<StoreKit.Transaction> = []
    
    func purchase(_ product: Product) async throws {
        let result = try await product.purchase()
        switch result {
        case .success(let verificationResult):
            if let transaction = try? verificationResult.payloadValue {
                activeTransactions.insert(transaction)
                await transaction.finish()
            }
        case .userCancelled:
            break
        case .pending:
            break
        @unknown default:
            break
        }
    }
}

每当购买结果处于成功状态时,都会提供一个 Transaction 类型的关联值,定义了成功的交易。StoreKit 将交易封装在 VerificationResult 类型中,允许我们验证交易是否正确签名并来自 App Store。

VerificationResult 类型由 StoreKit 2 用于验证数据是否有效且由 App Store 签名。它提供了 payloadValue 计算属性,我们可以使用它来解包已签名数据,或者如果数据未正确签名,则引发错误。

一旦获取了交易,应该解锁用户购买的功能,并在特定交易上调用 finish 函数。请记住,只有在解锁已购买的功能后才应该完成交易。

swift 复制代码
struct ContentView: View {
    @StateObject private var store = Store()
    
    var body: some View {
        VStack {
            Text("Purchased items: \(store.activeTransactions.count)")
            
            if store.products.isEmpty {
                ProgressView()
            } else {
                if store.activeTransactions.isEmpty {
                    ForEach(store.products, id: \.id) { product in
                        Button {
                            Task {
                                try await store.purchase(product)
                            }
                        } label: {
                            VStack {
                                Text(verbatim: product.displayName)
                                    .font(.headline)
                                Text(verbatim: product.displayPrice)
                            }
                        }
                        .buttonStyle(.borderedProminent)
                    }
                }
            }
        }
        .task {
            await store.fetchProducts()
        }
    }
}

当启用"询问购买"时,购买将变为挂起状态。在这种情况下,交易稍后才会到达,只有在父母批准后才会到达。应该观察 Transaction.updates 流来处理这种类型的交易。我们必须在应用程序启动时开始监视此流,以确保不会错过任何交易。

swift 复制代码
@MainActor final class Store: ObservableObject {
    @Published private(set) var activeTransactions: Set<StoreKit.Transaction> = []
    private var updates: Task<Void, Never>?
    
    // ...
    
    init() {
        updates = Task {
            for await update in StoreKit.Transaction.updates {
                if let transaction = try? update.payloadValue {
                    activeTransactions.insert(transaction)
                    await transaction.finish()
                }
            }
        }
    }
    
    deinit {
        updates?.cancel()
    }
}

StoreKit 2 提供了一种轻松获取所有活跃订阅和已购买产品的方法。Transaction 类型上的 currentEntitlements 属性列出了所有活跃订阅和未退款的产品。

swift 复制代码
@MainActor final class Store: ObservableObject {
    @Published private(set) var activeTransactions: Set<StoreKit.Transaction> = []
    // ...
    
    func fetchActiveTransactions() async {
        var activeTransactions: Set<StoreKit.Transaction> = []
        
        for await entitlement in StoreKit.Transaction.currentEntitlements {
            if let transaction = try? entitlement.payloadValue {
                activeTransactions.insert(transaction)
            }
        }
        
        self.activeTransactions = activeTransactions
    }
}

我们可以使用 currentEntitlements 属性来获取每次应用程序启动或更频繁时的所有活跃购买。通过主动监视 currentEntitlements 属性,我们消除了还原购买的需求,因为 currentEntitlements 始终包含最新的活跃订阅和非消耗性购买列表,即使它们是在另一台设备上购买的也是如此。

swift 复制代码
@main
struct MyApp: App {
    @Environment(\.scenePhase) private var scenePhase
    @StateObject private var store = Store()
    
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(store)
                .task(id: scenePhase) {
                    if scenePhase == .active {
                        await store.fetchActiveTransactions()
                    }
                }
        }
    }
}

总结

这篇文章介绍了如何在 iOS 应用中使用 StoreKit 2 实现应用内购买和订阅功能。主要内容包括项目配置、构建 Paywall 功能、显示产品列表、购买产品、处理交易状态、监控交易更新和获取活跃订阅与购买。通过详细的示例和解释,开发者可以轻松了解如何利用 StoreKit 2 构建强大的应用内购买功能。

相关推荐
denggun123451 天前
卡顿监测原理
macos·ios·xcode
Swift社区1 天前
用 Task Local Values 构建 Swift 里的依赖容器:一种更轻量的依赖注入思路
开发语言·ios·swift
TouchWorld1 天前
iOS逆向-哔哩哔哩增加3倍速播放(4)- 竖屏视频·全屏播放场景
ios·swift
ChineHe1 天前
Gin框架入门篇002_第一个Gin服务
macos·xcode·gin
1024小神2 天前
swift中使用ObservableObject单利模式的时候,用let 或 @ObservedObject 或 @StateObject 有什么区别
开发语言·ios·swift
1024小神2 天前
swift中 列表、字典、集合、元祖 常用的方法
数据结构·算法·swift
TouchWorld2 天前
iOS逆向-哔哩哔哩增加3倍速播放(3)-[横屏视频-全屏播放]场景
ios·swift
ShawnRacine3 天前
iOS开发-安装cocoapods
ios·xcode·cocoapods
东坡肘子3 天前
周日小插曲 -- 肘子的 Swift 周报 #115
人工智能·swiftui·swift