跳至正文
StoreKit — SubscriptionStoreView

SubscriptionStoreView

好的,我们来详细了解一下 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

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

本文共 2667 字,创建于 May 8, 2025

相关标签:Xcode, ByAI, StoreKit, Gemini

博客助手

正在打开博客助手…