专为 SwiftUI 设计的轻量级 StoreKit2 包装器,让应用内购买的实现更加简单。
请参阅 DevTutor 中详细的 StoreKitHelper 文档,其中包括多个快速入门示例、自定义支付界面示例和 API 参考,提供全面的示例和指导。
- 🚀 SwiftUI 原生: 专为 SwiftUI 设计,支持
@ObservableObject和@EnvironmentObject - 💡 简洁 API: 干净直观的应用内购买管理接口
- 🔄 自动更新: 实时交易监控和状态更新
- 🎟️ 优惠代码兑换: 内置支持 Apple 系统优惠代码兑换面板
- ✅ 类型安全: 基于协议的产品定义,提供编译时安全性
- 🧪 可测试: 完全可测试的架构,测试用例覆盖 ExampleTests.swift/StoreKitHelperTests.swift
在 SwiftUI 应用程序的入口点创建并注入一个 StoreContext 实例,它负责加载产品列表和跟踪购买状态。
import StoreKitHelper
enum AppProduct: String, InAppProduct {
case lifetime = "focuscursor.lifetime"
case monthly = "focuscursor.monthly"
var id: String { rawValue }
}
@main struct DevTutorApp: App {
@StateObject var store = StoreContext(products: AppProduct.allCases)
var body: some Scene {
WindowGroup {
ContentView().environmentObject(store)
}
}
}StoreContext 现在提供三态购买状态。应用启动时,purchaseStatus 会先处于 .loading,直到 Transaction.currentEntitlements 首次同步完成。在这段时间里,hasPurchased 和 hasNotPurchased 都会返回 false,从而避免启动阶段被误判成“未购买”。
switch store.purchaseStatus {
case .loading:
// 正在同步购买状态
case .purchased:
// ✅ 用户存在有效购买
case .notPurchased:
// 🧾 用户当前没有有效购买
}推荐写法:
@EnvironmentObject var store: StoreContext
var body: some View {
switch store.purchaseStatus {
case .loading:
ProgressView("正在检查购买状态...")
case .purchased:
// ✅ 用户已购买 - 显示完整功能
case .notPurchased:
// 🧾 用户未购买 - 显示受限内容或提示购买
}
}默认购买调用方式保持不变:
await store.purchase(product)如果需要传递 StoreKit 的购买选项,现在也可以直接透传:
await store.purchase(product, options: [
.appAccountToken(appAccountToken)
])兼容旧写法:
@EnvironmentObject var store: StoreContext
var body: some View {
if store.hasResolvedPurchaseStatus == false {
ProgressView("正在检查购买状态...")
} else if store.hasNotPurchased == true {
// 🧾 用户未购买 - 显示受限内容或提示购买
} else if store.hasPurchased == true {
// ✅ 用户已购买 - 显示完整功能
}
}StoreKitHelperView 和 StoreKitHelperSelectionView 会在支持的平台上显示“兑换优惠代码”入口。该入口支持 iOS 16+、macOS 15+、visionOS 1+。如果应用最低兼容 macOS 14,macOS 14 上不会显示该入口,也不会调用不支持的系统 API,因此可以继续正常编译和运行。
你也可以手动展示 Apple 的系统优惠代码兑换面板:
@EnvironmentObject var store: StoreContext
Button("兑换优惠代码") {
store.presentOfferCodeRedemption()
}兑换完成后,StoreContext 会重新同步当前有效权益,并更新 purchasedProductIDs / purchaseStatus。
如果你需要在包外部读取 StoreKitHelper 自带的国际化文案,请使用 StoreKitHelperL18n.localized(...)。
这个 API 会从包自身的资源 bundle 中取值,同时避免与主项目里自定义的 String 扩展发生符号冲突。
import StoreKitHelper
let locale = Locale(identifier: Locale.preferredLanguages.first ?? "en")
let title = StoreKitHelperL18n.localized(
key: "purchase_failed",
locale: locale
)使用 StoreKitHelperView 直接显示应用内购买弹窗视图,并通过链式 API 配置各种参数。
struct PurchaseContent: View {
@EnvironmentObject var store: StoreContext
var body: some View {
let locale: Locale = Locale(identifier: Locale.preferredLanguages.first ?? "en")
StoreKitHelperView()
.environment(\.locale, .init(identifier: locale.identifier))
.environment(\.pricingContent, { AnyView(PricingContent()) })
.environment(\.popupDismissHandle, {
// 弹窗被关闭时触发(例如用户点击关闭按钮)
store.isShowingPurchasePopup = false
})
.environment(\.termsOfServiceHandle, {
// 点击【服务条款】按钮时触发的操作
})
.environment(\.privacyPolicyHandle, {
// 点击【隐私政策】按钮时触发的操作
})
.frame(maxWidth: 300)
.frame(minWidth: 260)
}
}点击打开付费产品列表界面。
struct ContentView: View {
@EnvironmentObject var store: StoreContext
var body: some View {
if store.hasNotPurchased == true, store.isLoading == false {
PurchasePopupButton()
.sheet(isPresented: $store.isShowingPurchasePopup) {
PurchaseContent()
}
}
}
}跟 StoreKitHelperView 差不多,选择购买项进行支付。
struct PurchaseContent: View {
@EnvironmentObject var store: StoreContext
var body: some View {
let locale: Locale = Locale(identifier: Locale.preferredLanguages.first ?? "en")
StoreKitHelperSelectionView()
.environment(\.locale, .init(identifier: locale.identifier))
.environment(\.pricingContent, { AnyView(PricingContent()) })
.environment(\.popupDismissHandle, {
// 弹窗被关闭时触发(例如用户点击关闭按钮)
store.isShowingPurchasePopup = false
})
.environment(\.termsOfServiceHandle, {
// 点击【服务条款】按钮时触发的操作
})
.environment(\.privacyPolicyHandle, {
// 点击【隐私政策】按钮时触发的操作
})
.frame(maxWidth: 300)
.frame(minWidth: 260)
}
}protocol InAppProduct: CaseIterable {
var id: String { get }
}enum PurchaseStatus {
case loading
case purchased
case notPurchased
}products: [Product]- 从 App Store 获取的可用产品列表purchasedProductIDs: Set<String>- 已购买产品标识符的集合purchaseStatus: PurchaseStatus- 当前购买状态:.loading、.purchased、.notPurchasedhasResolvedPurchaseStatus: Bool- 首次购买状态同步是否已完成hasNotPurchased: Bool- 在购买状态完成解析后,用户是否未购买任何产品hasPurchased: Bool- 在购买状态完成解析后,用户是否已购买任何产品isLoading: Bool- 产品是否正在加载中isShowingOfferCodeRedemption: Bool- 是否正在展示系统优惠代码兑换面板errorMessage: String?- 当前错误信息(如有)
purchase(_ product: Product, options: Set<Product.PurchaseOption> = [])- 购买指定产品,并可选透传 StoreKit 购买参数restorePurchases()- 恢复之前的购买presentOfferCodeRedemption()- 在支持的平台上展示 Apple 系统优惠代码兑换面板refreshPurchasedProducts()- 重新同步当前有效权益和购买状态isPurchased(_ productID: ProductID) -> Bool- 根据 ID 检查产品是否已购买isPurchased(_ product: InAppProduct) -> Bool- 检查产品是否已购买product(for productID: ProductID) -> Product?- 根据 ID 获取产品product(for product: InAppProduct) -> Product?- 根据 InAppProduct 获取产品
基于 MIT 许可证授权。
