visionOS内购适配咨询:iOS16与iOS17内购代码兼容方案
原本在生产环境正常运行的内购代码在visionOS中无法使用,报错信息:
'purchase(options:)' is unavailable in visionOS: Use @Environment(.purchase) to get a PurchaseAction value to call. If your app uses UIKit, use purchase(confirmIn:options:)
原有内购代码片段:
func purchase(_ product: Product) async throws -> Transaction? { let result = try await product.purchase() //TEMPORARY JAN-23 switch result { case .success(let verification): // Successful purchase //Check whether the transaction is verified. If it isn't, //this function rethrows the verification error. let transaction = try checkVerified(verification) //The transaction is verified. Deliver content to the user. await updateCustomerProductStatus() // 省略其他case处理 }
尝试改用iOS 17的内购方式时,遇到版本兼容问题:直接声明@Environment(.purchase)会提示“'purchase' is only available in iOS 17.0 or newer”,给该属性添加@available(iOS 17.0, *)标注又报错“Stored properties cannot be marked potentially unavailable with '@available'”。需要修改代码以适配visionOS,同时兼容iOS 16和iOS 17版本。
1. 条件编译拆分多版本逻辑
通过系统版本和平台的条件编译,分别实现对应环境的购买逻辑:
- iOS 16及更早版本、非visionOS平台:保留原有的
product.purchase()调用 - iOS 17+ 和 visionOS:使用
@Environment(.purchase)提供的PurchaseAction
2. 解决@Environment的版本兼容限制
由于存储属性无法标记@available,可以用计算属性+条件编译在View中获取PurchaseAction:
private var purchaseAction: PurchaseAction? { if #available(iOS 17.0, visionOS 1.0, *) { return self[Environment(\.purchase)] } else { return nil } }
也可以将购买逻辑封装到独立的工具类中,通过依赖注入传递PurchaseAction,减少View内的版本判断代码。
3. 重构购买方法实现多版本兼容
完整的兼容版购买方法示例:
func purchase(_ product: Product) async throws -> Transaction? { if #available(iOS 17.0, visionOS 1.0, *) { guard let purchaseAction = purchaseAction else { throw PurchaseError.actionUnavailable } let verificationResult = try await purchaseAction.purchase(product) switch verificationResult { case .success(let verification): let transaction = try checkVerified(verification) await updateCustomerProductStatus() await transaction.finish() return transaction case .userCancelled, .pending: return nil } } else { let result = try await product.purchase() switch result { case .success(let verification): let transaction = try checkVerified(verification) await updateCustomerProductStatus() await transaction.finish() return transaction case .userCancelled, .pending: return nil @unknown default: throw PurchaseError.unknownResult } } } // 自定义错误类型,统一处理异常 enum PurchaseError: LocalizedError { case actionUnavailable case unknownResult case verificationFailed var errorDescription: String? { switch self { case .actionUnavailable: return "购买操作不可用" case .unknownResult: return "未知的购买结果" case .verificationFailed: return "交易验证失败" } } }
4. UIKit项目适配visionOS
如果是UIKit项目,visionOS上需要调用purchase(confirmIn:options:),同样通过条件编译区分:
func purchase(_ product: Product, presentingVC: UIViewController) async throws -> Transaction? { if #available(visionOS 1.0, *) { let result = try await product.purchase(confirmIn: presentingVC) // 复用验证和交付逻辑 switch result { case .success(let verification): let transaction = try checkVerified(verification) await updateCustomerProductStatus() await transaction.finish() return transaction case .userCancelled, .pending: return nil } } else if #available(iOS 17.0, *) { // iOS 17+ UIKit可使用PurchaseAction,需通过UIViewController的window获取环境 let purchaseAction = presentingVC.view.window?.environment(\.purchase) guard let action = purchaseAction else { throw PurchaseError.actionUnavailable } let verificationResult = try await action.purchase(product) // 后续逻辑同前 } else { // iOS 16及更早逻辑,同原有代码 let result = try await product.purchase() // ... } }
5. 复用通用验证逻辑
将checkVerified抽为独立方法,避免重复代码:
func checkVerified<T>(_ result: VerificationResult<T>) throws -> T { switch result { case .unverified(_, let error): throw error ?? PurchaseError.verificationFailed case .verified(let safe): return safe } }
内容的提问来源于stack exchange,提问作者Nat Serrano

