将 Adapty iOS SDK 迁移至 v4.0
Adapty iOS SDK 4.0(测试版)引入了流程概念,并对付费墙相关 API 进行了相应重命名。新 API 同时兼容全新的 Flow Builder 和现有的付费墙编辑工具——无需在 Adapty 看板侧进行任何配置变更。
快速参考
| v3 | v4 |
|---|---|
Adapty.getPaywall(placementId:locale:) | Adapty.getFlow(placementId:) |
AdaptyUI.getPaywallConfiguration(forPaywall:) | AdaptyUI.getFlowConfiguration(forFlow:locale:) |
Adapty.getPaywallProducts(paywall:) | Adapty.getPaywallProducts(flow:) |
Adapty.logShowPaywall(_:) | Adapty.logShowFlow(_:) |
AdaptyPaywallController | AdaptyFlowController |
AdaptyPaywallControllerDelegate | AdaptyFlowControllerDelegate |
AdaptyUI.paywallController(with:delegate:) | AdaptyUI.flowController(with:delegate:) |
.paywall() (SwiftUI modifier) | .flow() |
AdaptyPaywallView | AdaptyFlowView |
didFailRenderingWith: / didFailRendering: | didReceiveError: |
didFinishPurchase(可选,成功后自动关闭) | didFinishPurchase(必选,不自动关闭) |
Adapty_KidsMode / AdaptyUI_KidsMode 包产品 | KidsMode 包特性 |
Adapty.updateAttribution(_:source:)(source: String) | Adapty.updateAttribution(_:source:)(source: AdaptyAttributionSource) |
Adapty.setIntegrationIdentifier(key:value:) | Adapty.setIntegrationIdentifier(_:)(AdaptyIntegrationIdentifier) |
最低 iOS 版本要求
Adapty iOS SDK 4.0 将最低部署目标从 iOS 13.0 提升至 iOS 15.0。在升级之前,请将项目的 iOS Deployment Target 设置为 15.0 或更高版本。
安装:不再支持 CocoaPods
Adapty iOS SDK 4.0 已放弃对 CocoaPods 的支持。请改用 Swift Package Manager 安装 SDK。
如果你的项目仍在使用 CocoaPods,请从 Podfile 中移除 Adapty 和 AdaptyUI pods,运行 pod install 将其清理,然后在 Xcode 中通过 File → Add Package Dependency,使用 https://github.com/adaptyteam/AdaptySDK-iOS.git 添加该包。
Kids 模式:独立产品替换为 Package Trait
在 v3 中,你通过选择独立的 Adapty_KidsMode 和 AdaptyUI_KidsMode Package 产品并重命名导入来启用 Kids 模式。从 4.0.0-beta.2 起,这些产品已被移除。Kids 模式现在是常规 Adapty Package 上名为 KidsMode 的 Swift Package Trait——启用后,整个 SDK 中的 IDFA 和 AdSupport 将被编译移除。
迁移步骤如下:
-
在 Choose Package Products 窗口中,选择常规的 Adapty 和 AdaptyUI 产品,而非 Adapty_KidsMode 和 AdaptyUI_KidsMode。
-
启用
KidsModetrait。在 Xcode 26.4 或更高版本中,在项目的 Package Dependencies 视图中为 AdaptySDK-iOS 依赖项启用该 trait。如果你在Package.swift中将 Adapty 添加为依赖项(需要swift-tools-version6.1 或更高版本),则在该文件中启用:
.package(
url: "https://github.com/adaptyteam/AdaptySDK-iOS.git",
exact: "4.0.0-beta.2",
traits: ["KidsMode"]
)
-
将您的导入改回常规模块:
- import Adapty_KidsMode - import AdaptyUI_KidsMode + import Adapty + import AdaptyUI
Xcode 26.4 之前的版本无法通过 UI 为 Xcode 项目启用 traits。在这种情况下,请添加一个本地 Swift 包,使其依赖 Adapty 并启用 KidsMode trait,然后让应用目标依赖该包。
已移除的 API
Adapty.getPaywallProductsWithoutDeterminingOffer(paywall:)— 已移除。所有产品现在均包含优惠信息,因此不再需要单独的资格验证步骤。AdaptyPaywallProductWithoutDeterminingOffer— 已移除。之前传递此类型的回调(例如didSelectProduct)现在改为传递AdaptyPaywallProduct。
App Store 促销应用内购买功能暂时移除
作为 StoreKit 2 迁移的一部分,Adapty iOS SDK 4.0 移除了对 App Store 促销应用内购买的支持。shouldAddStorePayment(for:) 代理方法及其接收的 AdaptyDeferredProduct 类型在 4.0 中不再可用。
此功能的移除是暂时的——促销应用内购买支持将在后续的 4.x 版本中回归。如果您的应用依赖促销应用内购买,请继续使用 iOS SDK 3.x,直到该功能恢复。
获取付费墙
getPaywall + getPaywallConfiguration → getFlow + getFlowConfiguration
返回类型从 AdaptyPaywall / AdaptyUI.PaywallConfiguration 变更为 AdaptyFlow / AdaptyUI.FlowConfiguration。locale 参数从 fetch 调用中移出,改为在 getFlowConfiguration 中传入:
- let paywall = try await Adapty.getPaywall(placementId: "YOUR_PLACEMENT_ID", locale: "en")
- let paywallConfiguration = try await AdaptyUI.getPaywallConfiguration(forPaywall: paywall)
+ let flow = try await Adapty.getFlow(placementId: "YOUR_PLACEMENT_ID")
+ let flowConfiguration = try await AdaptyUI.getFlowConfiguration(forFlow: flow, locale: "en")
getPaywallProducts(paywall:) → getPaywallProducts(flow:)
getPaywallProducts 现在接受由 Adapty.getFlow 返回的 AdaptyFlow:
- let products = try await Adapty.getPaywallProducts(paywall: paywall)
+ let products = try await Adapty.getPaywallProducts(flow: flow)
追踪付费墙展示
logShowPaywall(:) → logShowFlow(:)
logShowPaywall 已重命名为 logShowFlow,现在接受 AdaptyFlow 而非 AdaptyPaywall。事件仍会记录在相同的实验变体下,因此现有的漏斗和 A/B 测试数据图表无需修改看板即可继续正常使用。
- try await Adapty.logShowPaywall(paywall)
+ try await Adapty.logShowFlow(flow)
与 v3 一样,在显示由流程编辑工具或付费墙编辑工具渲染的流程或付费墙时,无需调用此方法——Adapty 会自动追踪这些浏览记录。
didFinishPurchase 现在是必填项
在 v3 中,didFinishPurchase 是可选的:如果你没有实现它,付费墙会在购买成功后自动关闭。从 4.0.0-beta.2 开始,这个默认的自动关闭逻辑已被移除,这样流程在购买成功后可以继续执行——例如,继续展示流程中剩余的页面。现在由你来决定购买后的行为:关闭页面,或者什么都不做以让流程继续。
- UIKit:
AdaptyFlowControllerDelegate的实现者必须实现didFinishPurchase—— 该方法不再提供默认实现。 - SwiftUI:
.flow(...)和AdaptyFlowView(...)的didFinishPurchase闭包现在为非可选类型,与didFailPurchase和didFinishRestore保持一致。
如需保持 v3 的行为,请自行关闭页面:
func flowController(
_ controller: AdaptyFlowController,
didFinishPurchase product: AdaptyPaywallProduct,
purchaseResult: AdaptyPurchaseResult
) {
if !purchaseResult.isPurchaseCancelled {
controller.dismiss(animated: true)
}
}
UIKit
AdaptyPaywallController → AdaptyFlowController
重命名控制器类型和工厂方法:
- let controller = try AdaptyUI.paywallController(
- with: paywallConfiguration,
- delegate: self
- )
+ let controller = try AdaptyUI.flowController(
+ with: flowConfiguration,
+ delegate: self
+ )
AdaptyPaywallControllerDelegate → AdaptyFlowControllerDelegate
重命名该协议并更新所有方法签名。请注意,didSelectProduct 现在接收 AdaptyPaywallProduct 而非已移除的 AdaptyPaywallProductWithoutDeterminingOffer,且 didFinishPurchase 现在必须实现 —— 它不再有默认实现。
- class YourClass: AdaptyPaywallControllerDelegate {
+ class YourClass: AdaptyFlowControllerDelegate {
- func paywallControllerDidAppear(_ controller: AdaptyPaywallController) { }
+ func flowControllerDidAppear(_ controller: AdaptyFlowController) { }
- func paywallControllerDidDisappear(_ controller: AdaptyPaywallController) { }
+ func flowControllerDidDisappear(_ controller: AdaptyFlowController) { }
- func paywallController(_ controller: AdaptyPaywallController,
- didPerform action: AdaptyUI.Action) { }
+ func flowController(_ controller: AdaptyFlowController,
+ didPerform action: AdaptyUI.Action) { }
- func paywallController(_ controller: AdaptyPaywallController,
- didSelectProduct product: AdaptyPaywallProductWithoutDeterminingOffer) { }
+ func flowController(_ controller: AdaptyFlowController,
+ didSelectProduct product: AdaptyPaywallProduct) { }
- func paywallController(_ controller: AdaptyPaywallController,
- didStartPurchase product: AdaptyPaywallProduct) { }
+ func flowController(_ controller: AdaptyFlowController,
+ didStartPurchase product: AdaptyPaywallProduct) { }
- func paywallController(_ controller: AdaptyPaywallController,
- didFinishPurchase product: AdaptyPaywallProduct,
- purchaseResult: AdaptyPurchaseResult) { }
+ func flowController(_ controller: AdaptyFlowController,
+ didFinishPurchase product: AdaptyPaywallProduct,
+ purchaseResult: AdaptyPurchaseResult) { }
- func paywallController(_ controller: AdaptyPaywallController,
- didFailPurchase product: AdaptyPaywallProduct,
- error: AdaptyError) { }
+ func flowController(_ controller: AdaptyFlowController,
+ didFailPurchase product: AdaptyPaywallProduct,
+ error: AdaptyError) { }
- func paywallControllerDidStartRestore(_ controller: AdaptyPaywallController) { }
+ func flowControllerDidStartRestore(_ controller: AdaptyFlowController) { }
- func paywallController(_ controller: AdaptyPaywallController,
- didFinishRestoreWith profile: AdaptyProfile) { }
+ func flowController(_ controller: AdaptyFlowController,
+ didFinishRestoreWith profile: AdaptyProfile) { }
- func paywallController(_ controller: AdaptyPaywallController,
- didFailRestoreWith error: AdaptyError) { }
+ func flowController(_ controller: AdaptyFlowController,
+ didFailRestoreWith error: AdaptyError) { }
- func paywallController(_ controller: AdaptyPaywallController,
- didFailRenderingWith error: AdaptyUIError) { }
+ func flowController(_ controller: AdaptyFlowController,
+ didReceiveError error: AdaptyUIError) { }
- func paywallController(_ controller: AdaptyPaywallController,
- didFailLoadingProductsWith error: AdaptyError) -> Bool { }
+ func flowController(_ controller: AdaptyFlowController,
+ didFailLoadingProductsWith error: AdaptyError) -> Bool { }
- func paywallController(_ controller: AdaptyPaywallController,
- didPartiallyLoadProducts failedIds: [String]) { }
+ func flowController(_ controller: AdaptyFlowController,
+ didPartiallyLoadProducts failedIds: [String]) { }
- func paywallController(_ controller: AdaptyPaywallController,
- didFinishWebPaymentNavigation product: AdaptyPaywallProduct?,
- error: AdaptyError?) { }
+ func flowController(_ controller: AdaptyFlowController,
+ didFinishWebPaymentNavigation product: AdaptyPaywallProduct?,
+ error: AdaptyError?) { }
}
SwiftUI
.paywall() 修饰符 → .flow()
重命名修饰符,更新配置参数名称,并添加现在必需的 didFinishPurchase 闭包:
@State var flowPresented = false // rename freely — the variable name is your choice
var body: some View {
Text("Hello, AdaptyUI!")
- .paywall(
+ .flow(
isPresented: $flowPresented,
- paywallConfiguration: paywallConfiguration,
+ flowConfiguration: flowConfiguration,
+ didFinishPurchase: { product, purchaseResult in /* dismiss, or do nothing to let the flow continue */ },
didFailPurchase: { product, error in /* handle the error */ },
didFinishRestore: { profile in /* check access level and dismiss */ },
didFailRestore: { error in /* handle the error */ },
- didFailRendering: { error in flowPresented = false }
+ didReceiveError: { error in flowPresented = false }
)
}
重命名后的回调触发时机与原来的 didFailRendering 相同,同时新增了流程脚本产生的运行时错误(AdaptyUIError 错误码 4105——.jsException 对应的 JavaScript 异常)。现有的处理器代码无需修改——只需重命名参数即可。
AdaptyPaywallView → AdaptyFlowView
重命名视图,更新配置参数,添加现在必需的 didFinishPurchase 闭包,并更新所有 didSelectProduct 闭包——它现在接收 AdaptyPaywallProduct,而不是已移除的 AdaptyPaywallProductWithoutDeterminingOffer:
- AdaptyPaywallView(
- paywallConfiguration: paywallConfiguration,
- didSelectProduct: { product: AdaptyPaywallProductWithoutDeterminingOffer in /* handle */ },
+ AdaptyFlowView(
+ flowConfiguration: flowConfiguration,
+ didSelectProduct: { product: AdaptyPaywallProduct in /* handle */ },
+ didFinishPurchase: { product, purchaseResult in /* dismiss, or do nothing to let the flow continue */ },
didFailPurchase: { product, error in /* handle the error */ },
didFinishRestore: { profile in /* check access level and dismiss */ },
didFailRestore: { error in /* handle the error */ },
- didFailRendering: { error in /* handle the error */ }
+ didReceiveError: { error in /* handle the error */ }
)
AdaptyUI 自定义资源
AdaptyUICustomVideoAsset
以下两项变更会影响所有已有的调用位置:
.player现在接受AVPlayer而非AVQueuePlayer。- 每个 case 新增了末尾参数
resolution: CGSize?。传入nil可保持现有行为;传入实际像素尺寸后,播放器可在视频加载前预留布局空间(宽高比 =width / height)。
- case file(url: URL, preview: AdaptyUICustomImageAsset?)
- case remote(url: URL, preview: AdaptyUICustomImageAsset?)
- case player(item: AVPlayerItem, player: AVQueuePlayer, preview: AdaptyUICustomImageAsset?)
+ case file(url: URL, preview: AdaptyUICustomImageAsset?, resolution: CGSize?)
+ case remote(url: URL, preview: AdaptyUICustomImageAsset?, resolution: CGSize?)
+ case player(item: AVPlayerItem, player: AVPlayer, preview: AdaptyUICustomImageAsset?, resolution: CGSize?)
归因与集成标识符
updateAttribution(_:source:)
source 参数的类型从 String 改为新的 AdaptyAttributionSource 类型,原来嵌套的 AdaptyProfile.AttributionSource 被重命名为顶层的 AdaptyAttributionSource。可以使用预定义的来源之一,也可以传入字符串字面量表示其他来源——AdaptyAttributionSource 遵循 ExpressibleByStringLiteral,因此现有的字符串字面量调用无需修改即可继续编译。
- try await Adapty.updateAttribution(attribution, source: "adjust")
+ try await Adapty.updateAttribution(attribution, source: .adjust)
预定义来源:.appleAds、.adjust、.appsflyer、.branch、.tenjin。如果来源存储在 String 变量中,请用 AdaptyAttributionSource(rawValue: yourSource) 包装。
setIntegrationIdentifier(_:)
setIntegrationIdentifier(key:value:) 已被替换为一个可变参数方法,支持传入一个或多个 AdaptyIntegrationIdentifier 值。请使用预定义的工厂方法,而非原始字符串键:
- try await Adapty.setIntegrationIdentifier(key: "appsflyer_id", value: uid)
+ try await Adapty.setIntegrationIdentifier(.appsflyerId(uid))
你可以在单次调用中设置多个标识符:
try await Adapty.setIntegrationIdentifier(
.appsflyerId(uid),
.adjustDeviceId(adid)
)
将每个旧的键字符串替换为其工厂方法:
| v3 key | v4 factory |
|---|---|
"adjust_device_id" | .adjustDeviceId(_:) |
"airbridge_device_id" | .airbridgeDeviceId(_:) |
"amplitude_user_id" | .amplitudeUserId(_:) |
"amplitude_device_id" | .amplitudeDeviceId(_:) |
"appmetrica_device_id" | .appmetricaDeviceId(_:) |
"appmetrica_profile_id" | .appmetricaProfileId(_:) |
"appsflyer_id" | .appsflyerId(_:) |
"branch_id" | .branchId(_:) |
"facebook_anonymous_id" | .facebookAnonymousId(_:) |
"firebase_app_instance_id" | .firebaseAppInstanceId(_:) |
"mixpanel_user_id" | .mixpanelUserId(_:) |
"one_signal_subscription_id" | .oneSignalSubscriptionId(_:) |
"one_signal_player_id" | .oneSignalPlayerId(_:) |
"posthog_distinct_user_id" | .posthogDistinctUserId(_:) |
"pushwoosh_hwid" | .pushwooshHWID(_:) |
"tenjin_analytics_installation_id" | .tenjinAnalyticsInstallationId(_:) |