Migrer le SDK iOS Adapty vers la v4.0

Le SDK iOS Adapty 4.0 introduit les flows et renomme les API paywall en conséquence. Les nouvelles API fonctionnent aussi bien avec le nouveau Flow Builder qu’avec le Paywall Builder existant — aucune modification de configuration n’est requise côté Adapty Dashboard.

Référence rapide

v3v4
Adapty.getPaywall(placementId:locale:)Adapty.getFlow(placementId:)
AdaptyUI.getPaywallConfiguration(forPaywall:)AdaptyUI.getFlowConfiguration(forFlow:locale:)
Adapty.getPaywallProducts(paywall:)Adapty.getPaywallProducts(flow:)
Adapty.logShowPaywall(_:)Adapty.logShowFlow(_:)
AdaptyPaywallControllerAdaptyFlowController
AdaptyPaywallControllerDelegateAdaptyFlowControllerDelegate
AdaptyUI.paywallController(with:delegate:)AdaptyUI.flowController(with:delegate:)
.paywall() (modificateur SwiftUI).flow()
AdaptyPaywallViewAdaptyFlowView
didFailRenderingWith: / didFailRendering:didReceiveError:
didFinishPurchase (optionnel, fermeture automatique en cas de succès)didFinishPurchase (requis, pas de fermeture automatique)
Produits de package Adapty_KidsMode / AdaptyUI_KidsModeTrait de package KidsMode
Adapty.updateAttribution(_:source:) (source: String)Adapty.updateAttribution(_:source:) (source: AdaptyAttributionSource)
Adapty.setIntegrationIdentifier(key:value:)Adapty.setIntegrationIdentifier(_:) (AdaptyIntegrationIdentifier)

Version iOS minimale

Adapty iOS SDK 4.0 fait passer la cible de déploiement minimale d’iOS 13.0 à iOS 15.0. Définissez la cible de déploiement iOS de votre projet à 15.0 ou une version ultérieure avant de procéder à la mise à niveau.

Installation : CocoaPods n’est plus pris en charge

Adapty iOS SDK 4.0 abandonne le support de CocoaPods. Installez le SDK avec Swift Package Manager.

Si votre projet utilise encore CocoaPods, supprimez les pods Adapty et AdaptyUI de votre Podfile, exécutez pod install pour les supprimer, puis ajoutez le package dans Xcode via File → Add Package Dependency en utilisant https://github.com/adaptyteam/AdaptySDK-iOS.git.

Mode Enfants : produits séparés remplacés par un trait de package

Dans la v3, vous activiez le Mode Enfants en sélectionnant les produits de package distincts Adapty_KidsMode et AdaptyUI_KidsMode et en renommant vos imports. Dans la v4.0, ces produits ont été supprimés. Le Mode Enfants est désormais un trait de package Swift nommé KidsMode sur le package Adapty standard — son activation exclut IDFA et AdSupport de l’ensemble du SDK à la compilation.

Pour migrer :

  1. Dans la fenêtre Choose Package Products, sélectionnez les produits standard Adapty et AdaptyUI au lieu de Adapty_KidsMode et AdaptyUI_KidsMode.

  2. Activez le trait KidsMode. Dans Xcode 26.4 ou version ultérieure, activez-le pour la dépendance AdaptySDK-iOS dans la vue Package Dependencies de votre projet. Si vous ajoutez Adapty en tant que dépendance dans Package.swift (nécessite swift-tools-version 6.1 ou version ultérieure), activez-le à cet endroit :

    .package(
        url: "https://github.com/adaptyteam/AdaptySDK-iOS.git",
        from: "4.0.0",
        traits: ["KidsMode"]
    )
  3. Remettez vos imports sur les modules standard :

   - import Adapty_KidsMode
   - import AdaptyUI_KidsMode
   + import Adapty
   + import AdaptyUI

Les versions de Xcode antérieures à 26.4 ne permettent pas d’activer les traits pour un projet Xcode depuis l’interface. Dans ce cas, ajoutez un petit package Swift local qui dépend d’Adapty avec le trait KidsMode activé, et faites dépendre votre cible d’application de ce package.

APIs supprimées

  • Adapty.getPaywallProductsWithoutDeterminingOffer(paywall:) — supprimée. Tous les produits incluent désormais les informations sur les offres, ce qui rend la vérification séparée d’éligibilité inutile.
  • AdaptyPaywallProductWithoutDeterminingOffer — supprimée. Les callbacks qui utilisaient auparavant ce type (comme didSelectProduct) transmettent maintenant AdaptyPaywallProduct.

Les achats intégrés promus sur l’App Store temporairement supprimés

Dans le cadre de la migration vers StoreKit 2, le SDK iOS Adapty 4.0 supprime la prise en charge des achats intégrés promus sur l’App Store. La méthode delegate shouldAddStorePayment(for:) et le type AdaptyDeferredProduct qu’elle reçoit ne sont pas disponibles dans la version 4.0.

Cette suppression est temporaire — la prise en charge des achats intégrés promus sera de retour dans une version ultérieure 4.x. Si votre application repose sur des achats intégrés promus, restez sur le SDK iOS 3.x jusqu’au retour de cette fonctionnalité.

Récupération des paywalls

getPaywall + getPaywallConfiguration → getFlow + getFlowConfiguration

Les types retournés passent de AdaptyPaywall / AdaptyUI.PaywallConfiguration à AdaptyFlow / AdaptyUI.FlowConfiguration. Le paramètre locale quitte l’appel de récupération et se déplace vers 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 prend désormais un AdaptyFlow retourné par Adapty.getFlow :

- let products = try await Adapty.getPaywallProducts(paywall: paywall)
+ let products = try await Adapty.getPaywallProducts(flow: flow)

Fichiers de secours

Le format du fichier de secours a changé avec le SDK v4. Téléchargez le nouveau fichier depuis Placements > Fallbacks et intégrez-le à votre application.

Suivi des vues de paywall

logShowPaywall(:) → logShowFlow(:)

logShowPaywall est renommé en logShowFlow et prend désormais un AdaptyFlow à la place d’un AdaptyPaywall. L’événement est toujours enregistré pour la même variation, donc les métriques de funnel et de test A/B existantes continuent de fonctionner sans modification du tableau de bord.

- try await Adapty.logShowPaywall(paywall)
+ try await Adapty.logShowFlow(flow)

Comme dans la v3, vous n’avez pas besoin d’appeler cette méthode lors de l’affichage des flows ou des paywalls générés par le Flow Builder ou le Paywall Builder — Adapty suit ces vues automatiquement.

didFinishPurchase est maintenant obligatoire

Dans la v3, didFinishPurchase était facultatif : si vous ne l’implémentiez pas, le paywall se fermait automatiquement après un achat réussi. Dans la v4.0, ce comportement de fermeture automatique par défaut a été supprimé afin qu’un flow puisse continuer après un achat réussi — par exemple, pour afficher les écrans restants de votre flow. Vous décidez désormais ce qui se passe après un achat : fermer l’écran, ou ne rien faire pour laisser le flow continuer.

  • UIKit : les conformeurs à AdaptyFlowControllerDelegate doivent implémenter didFinishPurchase — cette méthode n’a plus d’implémentation par défaut.
  • SwiftUI : la closure didFinishPurchase de .flow(...) et AdaptyFlowView(...) est désormais non-optionnelle, au même titre que didFailPurchase et didFinishRestore.

Pour conserver le comportement de la v3, fermez l’écran vous-même :

func flowController(
    _ controller: AdaptyFlowController,
    didFinishPurchase product: AdaptyPaywallProduct,
    purchaseResult: AdaptyPurchaseResult
) {
    if !purchaseResult.isPurchaseCancelled {
        controller.dismiss(animated: true)
    }
}

UIKit

AdaptyPaywallController → AdaptyFlowController

Renommez le type de contrôleur et la méthode factory :

- let controller = try AdaptyUI.paywallController(
-     with: paywallConfiguration,
-     delegate: self
- )
+ let controller = try AdaptyUI.flowController(
+     with: flowConfiguration,
+     delegate: self
+ )

AdaptyPaywallControllerDelegate → AdaptyFlowControllerDelegate

Renommez le protocole et mettez à jour chaque signature de méthode. Notez que didSelectProduct reçoit désormais AdaptyPaywallProduct au lieu de l’AdaptyPaywallProductWithoutDeterminingOffer supprimé, et didFinishPurchase doit maintenant être implémenté — il n’a plus d’implémentation par défaut.

- 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

Modificateur .paywall().flow()

Renommez le modificateur, mettez à jour le nom du paramètre de configuration, et ajoutez la closure didFinishPurchase (désormais obligatoire) :

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

Le callback renommé se déclenche pour les mêmes erreurs de rendu que didFailRendering, plus les nouvelles erreurs d’exécution provenant du script de flow (exceptions JavaScript avec le code AdaptyUIError 4105.jsException). Les corps de handler existants ne nécessitent aucune modification — il suffit de renommer le paramètre.

AdaptyPaywallView → AdaptyFlowView

Renommez la vue, mettez à jour le paramètre de configuration, ajoutez la closure didFinishPurchase (désormais obligatoire), et mettez à jour toute closure didSelectProduct — elle reçoit maintenant AdaptyPaywallProduct à la place du type supprimé 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 */ }
 )

Ressources personnalisées AdaptyUI

AdaptyUICustomVideoAsset

Deux changements affectent tous les appels existants :

  • .player accepte désormais AVPlayer au lieu de AVQueuePlayer.
  • Chaque cas a reçu un paramètre supplémentaire resolution: CGSize? en fin de signature. Passez nil pour conserver le comportement actuel, ou indiquez la taille réelle en pixels afin que le lecteur puisse réserver l’espace de mise en page (ratio = width / height) avant le chargement de la vidéo.
- 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?)

Identifiants d’attribution et d’intégration

updateAttribution(_:source:)

Le paramètre source passe du type String au nouveau type AdaptyAttributionSource, et l’ancien AdaptyProfile.AttributionSource imbriqué est renommé en AdaptyAttributionSource au niveau supérieur. Utilisez l’une des sources prédéfinies, ou passez un littéral de chaîne pour toute autre source — AdaptyAttributionSource est conforme à ExpressibleByStringLiteral, donc les appels existants avec des littéraux de chaîne continuent de compiler.

- try await Adapty.updateAttribution(attribution, source: "adjust")
+ try await Adapty.updateAttribution(attribution, source: .adjust)

Sources prédéfinies : .appleAds, .adjust, .appsflyer, .branch, .tenjin. Si vous conservez la source dans une variable String, encapsulez-la : AdaptyAttributionSource(rawValue: yourSource).

setIntegrationIdentifier(_:)

setIntegrationIdentifier(key:value:) est remplacé par une méthode variadique qui accepte une ou plusieurs valeurs AdaptyIntegrationIdentifier. Utilisez les méthodes factory prédéfinies plutôt que des clés de type chaîne brute :

- try await Adapty.setIntegrationIdentifier(key: "appsflyer_id", value: uid)
+ try await Adapty.setIntegrationIdentifier(.appsflyerId(uid))

Vous pouvez définir plusieurs identifiants en un seul appel :

try await Adapty.setIntegrationIdentifier(
    .appsflyerId(uid),
    .adjustDeviceId(adid)
)

Remplacez chaque ancienne chaîne de clé par sa méthode factory correspondante :

v3 keyv4 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(_:)