StoreKit — SubscriptionStoreView
This article is extracted from the chat log with AI. Please identify it with caution.

好的,我们来详细了解一下 SubscriptionStoreView,这是苹果在 StoreKit 中引入的一个强大的 SwiftUI 组件,旨在极大简化应用内订阅购买界面的构建。

SubscriptionStoreView 是什么?

SubscriptionStoreView 是一个预构建的 SwiftUI 视图,它可以自动加载并展示你在 App Store Connect 中配置的自动续订订阅产品。它负责处理产品的显示、价格的本地化、购买流程的发起,甚至包括一些常见的 UI 元素,如购买按钮、服务条款链接等。

核心优势与使用场景

  • 快速实现订阅购买界面: 对于开发者来说,最大的好处是无需从头手动构建复杂的订阅展示 UI。SubscriptionStoreView 提供了多种标准化的样式,可以快速搭建出符合苹果设计规范的购买页面。
  • 自动适配与本地化: 该视图会自动拉取 App Store Connect 中配置的产品信息(名称、描述、价格),并根据用户设备的地区设置进行本地化显示。
  • 标准化用户体验: 使用 SubscriptionStoreView 可以为用户提供一个熟悉且一致的订阅购买体验,减少用户的学习成本。
  • 处理复杂逻辑: 它内部处理了产品加载、价格显示、促销优惠(如果配置了)、家庭共享信息以及购买流程的调用。
  • 减少维护成本: 由于 UI 和部分逻辑由苹果提供和维护,可以减少开发者因 StoreKit API 变更或 UI 规范更新带来的维护工作。

主要使用场景:

  1. 应用主付费墙 (Paywall): 当用户首次遇到需要订阅才能访问的功能时,展示 SubscriptionStoreView 来呈现所有可用的订阅选项。
  2. 应用内升级/追加销售页面: 对于已在使用免费版或低级别订阅的用户,可以使用此视图展示更高级别的订阅选项。
  3. 用户引导流程 (Onboarding): 在新用户引导流程的最后,展示订阅选项,鼓励用户订阅。
  4. 设置或账户页面: 在应用的设置或用户账户区域提供一个入口,让用户可以方便地查看和管理(或发起新的)订阅。

可用性

SubscriptionStoreView 是在 iOS 17, iPadOS 17, macOS 14 (Sonoma), watchOS 10, tvOS 17, visionOS 1 及更高版本中引入的。因此,如果你的应用需要支持更早的操作系统版本,则无法直接使用它,需要自行构建 UI 或使用旧的 StoreKit API。

如何使用 SubscriptionStoreView

使用 SubscriptionStoreView 非常直接。你主要需要提供一个订阅组 ID (Subscription Group ID),这是你在 App Store Connect 中为一组订阅产品定义的标识符。

Swift

import SwiftUI
import StoreKit

struct MySubscriptionPaywallView: View {
    // 替换成你在 App Store Connect 中配置的订阅组 ID
    let subscriptionGroupID = "YOUR_SUBSCRIPTION_GROUP_ID"

    // 可选:如果你想优先展示或只展示特定的产品ID
    // let productIDsToShow: [String] = ["com.example.monthly", "com.example.yearly"]

    @Environment(\.dismiss) var dismiss

    var body: some View {
        // 基本用法:提供订阅组 ID
        SubscriptionStoreView(groupID: subscriptionGroupID) {
            // 在这里可以放置你的 App Icon, 营销文本等作为视图的背景或前景内容
            // 这部分内容会与 StoreKit 渲染的订阅选项结合显示
            VStack {
                Image("MyAppIcon") // 示例
                    .resizable()
                    .frame(width: 80, height: 80)
                    .clipShape(RoundedRectangle(cornerRadius: 16))
                Text("Unlock All Features!")
                    .font(.largeTitle.bold())
                Text("Subscribe now to get access to exclusive content and features.")
                    .font(.subheadline)
                    .multilineTextAlignment(.center)
                    .padding(.horizontal)
            }
            .padding(.vertical)
        }
        // 可选:只展示特定的产品 (如果产品ID列表不为空)
        // .products(productIDsToShow.isEmpty ? nil : productIDsToShow.map { Product.ID($0) })

        // ----- 自定义修饰符 -----

        // 1. 自定义购买按钮标签
        .subscriptionStoreButtonLabel(.multiline) // 或者 .automatic, .singleLine

        // 2. 自定义订阅选项的背景 (例如,让每个选项卡片化)
        .subscriptionStorePickerItemBackground(.thinMaterial) // 或者 .regularMaterial, .thickMaterial, .ultraThinMaterial, .ultraThickMaterial, Color.gray.opacity(0.1)

        // 3. 控制订阅选项的样式
        .subscriptionStoreControlStyle(.automatic) // 或者 .picker, .prominentPicker (iOS 17.2+)

        // 4. 提供服务条款和隐私政策链接 (强烈推荐)
        .subscriptionStorePolicyDestination(for: .privacyPolicy) {
            // 跳转到你的隐私政策视图或 URL
            MyPrivacyPolicyView()
        }
        .subscriptionStorePolicyDestination(for: .termsOfService) {
            // 跳转到你的服务条款视图或 URL
            MyTermsOfServiceView()
        }
        // 也可以直接打开 URL
        // .subscriptionStorePolicyDestination(for: .privacyPolicy, destination: { URL(string: "https://example.com/privacy")! })

        // 5. 提供登录入口 (如果你的应用有账户系统,且订阅与账户关联)
        .subscriptionStoreSignInAction {
            // 处理登录逻辑,例如弹出一个登录视图
            print("Sign In button tapped")
            // showLoginView = true
        }
        
        // 6. (iOS 17.4+, macOS 14.4+) 营销内容自定义
        .subscriptionStoreMarketingContent {
            // 此闭包允许你提供一个完全自定义的营销内容视图
            // 它会替代 StoreKit 默认从 App Store Connect 拉取的营销内容(如果已配置)
            // 或者在 SubscriptionStoreView 的内容闭包之外提供主要的营销信息
            VStack {
                Text("Special Offer!")
                    .font(.headline)
                Text("Get 50% off for the first 3 months.")
                    .font(.caption)
            }
        }

        // 7. (iOS 17.5+, macOS 14.5+) 自定义视图可见性时的回调
        .onSubscriptionStoreViewAppear { subscriptionStoreViewProxy in
            print("SubscriptionStoreView did appear. Products: \(subscriptionStoreViewProxy.products.count)")
            // subscriptionStoreViewProxy 允许你访问视图内部的一些状态,比如加载的产品
        }
        .onSubscriptionStoreViewDisappear { subscriptionStoreViewProxy in
            print("SubscriptionStoreView did disappear.")
        }

        // 8. (iOS 17.5+, macOS 14.5+) 购买结果处理
        // 虽然 SubscriptionStoreView 内部处理大部分购买逻辑,
        // 你仍然可以通过标准的 StoreKit 方式监听交易或使用此回调。
        .onInAppPurchaseCompletion { product, result in
            // 这个回调在购买流程完成时被调用 (无论成功、失败还是取消)
            // 注意:这并不是替代 Transaction.updates 或其他 StoreKit 核心机制,
            // 而是 SubscriptionStoreView 特有的一个便捷回调。
            // 你仍然需要正确处理 Transaction 和完成交易 (transaction.finish())。
            print("Purchase completed for product: \(product.id)")
            switch result {
            case .success(let verificationResult):
                print("Purchase successful (Verification: \(verificationResult))")
                // 在这里你可以触发 UI 更新,例如关闭付费墙
                // dismiss()
                // **重要**: 确保你的 StoreManager 或其他地方仍在监听 Transaction.updates
                // 并正确处理和 finish() 该交易。
                // 此回调仅为通知,不替代核心交易处理。
                Task {
                    // 假设你有一个 StoreManager 来处理交易和状态更新
                    // await storeManager.handlePurchaseResult(verificationResult)
                }
            case .userCancelled:
                print("Purchase cancelled by user.")
            case .pending:
                print("Purchase pending.")
            @unknown default:
                print("Unknown purchase completion result.")
            }
        }
        // (iOS 17.5+, macOS 14.5+) 也可以只处理成功购买
        // .onInAppPurchaseCompletion(for: .success) { product, transaction in
        //     print("Successfully purchased \(product.displayName)")
        //     Task {
        //         await transaction.finish() // 示例:直接完成,但通常应在StoreManager中
        //         dismiss()
        //     }
        // }
        
        // 添加一个关闭按钮
        .toolbar {
            ToolbarItem(placement: .cancellationAction) {
                Button("Dismiss") {
                    dismiss()
                }
            }
        }
        // .navigationTitle("Go Premium") // 如果在 NavigationView 中
    }
}

// 示例:隐私政策和服务条款视图
struct MyPrivacyPolicyView: View {
    var body: some View { Text("Your Privacy Policy content here.").padding() }
}
struct MyTermsOfServiceView: View {
    var body: some View { Text("Your Terms of Service content here.").padding() }
}

关键点说明:

  • groupID: 这是必需的。SubscriptionStoreView 会查找属于此组的所有有效订阅产品。
  • products(for:) (可选的 StoreKit 方法): 虽然 SubscriptionStoreView 会自动加载产品,但你仍然可以使用 Product.products(for:) 预先加载产品,或者在 StoreManager 中管理它们,SubscriptionStoreView 也能利用这些已加载的产品。
  • 内容闭包 SubscriptionStoreView { ... }: 你可以在这个闭包中提供自定义的营销视图 (Header content),例如 App 图标、标题、特性列表等。这部分内容会显示在订阅选项的上方或周围,具体布局取决于所选的样式和平台。
  • subscriptionStoreButtonLabel: 控制购买按钮上文本的显示方式,例如是否允许换行。
  • subscriptionStorePickerItemBackground: 改变每个订阅选项卡片(如果样式是 picker 类型)的背景,可以用于品牌化或提升视觉效果。
  • subscriptionStoreControlStyle: (iOS 17.2+) 允许你选择不同的控件样式:
    • .automatic: 系统自动选择最合适的样式。
    • .picker: 将订阅选项显示为类似 Picker 的列表,用户可以选择其一。
    • .prominentPicker: 更为突出显示的 Picker 样式,通常每个选项占据更大空间,更像卡片。
  • subscriptionStorePolicyDestination: 设置隐私政策和服务条款的跳转目标。这对于符合 App Store 指南至关重要。目标可以是另一个 SwiftUI 视图,也可以是一个 URL。
  • subscriptionStoreSignInAction: 如果你的订阅是和用户账户绑定的,可以通过这个修饰符提供一个登录入口。
  • subscriptionStoreMarketingContent (iOS 17.4+): 这个闭包允许你完全自定义显示在订阅选项上方的营销内容区域。如果 App Store Connect 中也配置了营销内容,你提供的视图会优先显示。
  • .onSubscriptionStoreViewAppear / .onSubscriptionStoreViewDisappear (iOS 17.5+): 这些回调让你可以在视图出现或消失时执行操作,SubscriptionStoreViewProxy 参数可以提供视图内部加载的产品信息。
  • .onInAppPurchaseCompletion (iOS 17.5+): 提供了一个便捷的方式来响应从 SubscriptionStoreView 内部发起的购买操作的结果。但请记住,这并不取代你的核心 StoreKit 交易处理逻辑 (例如,在 StoreManager 中监听 Transaction.updates 并调用 transaction.finish())。 此回调更多是用于 UI 响应,例如购买成功后关闭视图。核心的交易验证和完成仍需遵循标准的 StoreKit 2 实践。

App Store Connect 配置

SubscriptionStoreView 的很多显示内容依赖于你在 App Store Connect 中的配置:

  • 订阅组 (Subscription Group): 必须设置。
  • 产品 (Products): 在订阅组内创建你的订阅产品(例如,月度、年度)。
  • 显示名称和描述 (Display Name & Description): 为每个产品提供清晰的名称和描述,这些会显示在 SubscriptionStoreView 中。
  • 价格 (Price Tiers): 设置价格。
  • App Store 促销图片 (Promotional Image - 可选): 在订阅组级别或产品级别可以上传促销图片,某些 SubscriptionStoreView 样式可能会展示这些图片。
  • 订阅优惠 (Introductory Offers, Promotional Offers): 如果配置了,SubscriptionStoreView 通常会自动展示这些优惠信息。
  • App Store 营销内容 (Marketing Content - 可选,在 App Store Connect 的“App 内购买” -> “营销内容”配置): 你可以为订阅组配置标题和副标题,SubscriptionStoreView 默认会拉取并展示这部分内容,除非你使用 .subscriptionStoreMarketingContent 修饰符提供了自定义内容。

注意事项和限制

  • 版本限制: 仅适用于较新的操作系统版本 (iOS 17+ 等)。
  • 自定义程度: 虽然提供了一些修饰符,但 SubscriptionStoreView 的整体布局和样式自定义程度不如完全手动构建的 UI。如果你的品牌或设计有非常特殊的需求,可能仍需自定义实现。
  • 依赖 App Store Connect: 很多显示内容直接来自 App Store Connect,确保那里的信息是最新的且准确的。
  • 测试: 在 Sandbox 环境中充分测试 SubscriptionStoreView 的显示和购买流程。

总而言之,SubscriptionStoreView 是一个非常受欢迎的补充,它显著降低了实现标准订阅购买页面的门槛,使得开发者可以将更多精力放在核心应用功能上,同时还能提供符合苹果设计规范的流畅用户体验。对于大多数需要展示订阅选项的应用来说,它都是一个值得优先考虑的方案。

本文共 3140 字,创建于 May 8, 2025
相关标签: Xcode, ByAI, StoreKit, Gemini