Skip to content

Latest commit

 

History

History
257 lines (213 loc) · 13.7 KB

File metadata and controls

257 lines (213 loc) · 13.7 KB
使用 我的应用 也是一种 支持 我的方式:
Scap: Screenshot & Markup Edit Screen Test Deskmark Keyzer Vidwall Hub VidCrop Vidwall Mousio Hint Mousio Musicer Audioer FileSentinel FocusCursor Videoer KeyClicker DayBar Iconed Menuist Quick RSS Quick RSS Web Serve Copybook Generator DevTutor for SwiftUI RegexMate Time Passage Iconize Folder Textsound Saver Create Custom Symbols DevHub Resume Revise Palette Genius Symbol Scribe

StoreKit Helper

English

专为 SwiftUI 设计的轻量级 StoreKit2 包装器,让应用内购买的实现更加简单。

StoreKit Helper

文档

请参阅 DevTutor 中详细的 StoreKitHelper 文档,其中包括多个快速入门示例、自定义支付界面示例和 API 参考,提供全面的示例和指导。

功能特性

  • 🚀 SwiftUI 原生: 专为 SwiftUI 设计,支持 @ObservableObject@EnvironmentObject
  • 💡 简洁 API: 干净直观的应用内购买管理接口
  • 🔄 自动更新: 实时交易监控和状态更新
  • 类型安全: 基于协议的产品定义,提供编译时安全性
  • 🧪 可测试: 完全可测试的架构,测试用例覆盖 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 首次同步完成。在这段时间里,hasPurchasedhasNotPurchased 都会返回 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

使用 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()
                }
        }
    }
}

StoreKitHelperSelectionView

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)
    }
}

API 参考

InAppProduct 协议

protocol InAppProduct: CaseIterable {
    var id: String { get }
}

PurchaseStatus

enum PurchaseStatus {
    case loading
    case purchased
    case notPurchased
}

StoreContext 属性

  • products: [Product] - 从 App Store 获取的可用产品列表
  • purchasedProductIDs: Set<String> - 已购买产品标识符的集合
  • purchaseStatus: PurchaseStatus - 当前购买状态:.loading.purchased.notPurchased
  • hasResolvedPurchaseStatus: Bool - 首次购买状态同步是否已完成
  • hasNotPurchased: Bool - 在购买状态完成解析后,用户是否未购买任何产品
  • hasPurchased: Bool - 在购买状态完成解析后,用户是否已购买任何产品
  • isLoading: Bool - 产品是否正在加载中
  • errorMessage: String? - 当前错误信息(如有)

StoreContext 方法

  • purchase(_ product: Product, options: Set<Product.PurchaseOption> = []) - 购买指定产品,并可选透传 StoreKit 购买参数
  • restorePurchases() - 恢复之前的购买
  • isPurchased(_ productID: ProductID) -> Bool - 根据 ID 检查产品是否已购买
  • isPurchased(_ product: InAppProduct) -> Bool - 检查产品是否已购买
  • product(for productID: ProductID) -> Product? - 根据 ID 获取产品
  • product(for product: InAppProduct) -> Product? - 根据 InAppProduct 获取产品

许可证

基于 MIT 许可证授权。