---
title: "Traiter les données des flows dans le SDK iOS"
description: "Enregistrez et utilisez les données saisies par vos utilisateurs dans les flows de votre application iOS avec le SDK Adapty."
---

> **AI agents**: to search Adapty docs faster and with fewer tokens, install the Adapty skill. Claude Code (self-updating via plugin): `claude plugin marketplace add adaptyteam/adapty-skills && claude plugin install adapty-skills@adapty` — other tools: `npx skills add adaptyteam/adapty-skills --all`

Lorsqu'un utilisateur saisit quelque chose dans un champ de saisie, répond à un quiz ou active un interrupteur dans un [flow](adapty-flow-builder), le SDK transmet la valeur à votre application via son callback d'analytics.

Les applications utilisent le plus souvent ces données pour :

- **Enregistrer les utilisateurs sur votre propre backend** : récupérez l'adresse e-mail et le nom saisis dans votre onboarding, puis créez leur compte à la fermeture du flow.
- **Sauvegarder les réponses et préférences** : suivez ce que l'utilisateur a choisi pour que votre app puisse en tenir compte plus tard — par exemple, [écrivez-les dans son profil Adapty](setting-user-attributes) comme attributs personnalisés.
- **Personnaliser les flows futurs** : enregistrez les réponses du quiz comme attributs personnalisés, puis ciblez un placement ultérieur pour que chaque segment voie un flow différent, ou un paywall différent à l'intérieur.
- **Alimenter des plateformes d'analytics tierces** : transmettez les réponses à Amplitude, Mixpanel ou tout autre outil d'analyse produit que vous utilisez.

Les champs de saisie et les groupes sélectionnables transmettent automatiquement leurs valeurs. Pour les distinguer dans votre code, attribuez à chaque champ un **Element ID** significatif et à chaque groupe sélectionnable un **Group ID** dans le builder.

## Avant de commencer

Vous avez besoin de :

- **Adapty SDK v4 ou supérieur** : Les callbacks de flow n'existent pas dans les versions antérieures.
- **Un flow créé dans le [Flow & Paywall Builder](adapty-flow-builder)** : Seuls les flows transmettent les valeurs d'entrée via ce callback.
- **Une version de flow publiée récemment** : Un flow ne transmet les valeurs d'entrée que si vous l'avez publié après que cette fonctionnalité soit devenue disponible. Si rien ne parvient à votre application, publiez une nouvelle version du flow et réessayez.

## Recevoir les valeurs saisies \{#receive-input-values\}

Les valeurs saisies arrivent dans le même callback que tous les autres événements analytiques d'un flow, sous le nom d'événement `flow_user_input`. Enregistrez le callback avec vos autres gestionnaires d'événements de flow.

<Tabs>
<TabItem value="swiftui" label="SwiftUI" default>

Passez une closure `didReceiveAnalyticEvent` au modificateur `.flow` :

```swift showLineNumbers title="Swift"
Text("Hello, AdaptyUI!")
    .flow(
        isPresented: $flowPresented,
        flowConfiguration: flowConfiguration,
        didFinishPurchase: { product, purchaseResult in /* handle the event */ },
        didFailPurchase: { product, error in /* handle the error */ },
        didFinishRestore: { profile in /* handle the event */ },
        didFailRestore: { error in /* handle the error */ },
        didReceiveError: { error in flowPresented = false },
        didReceiveAnalyticEvent: { name, params in
            handleFlowInput(name: name, params: params)
        }
    )
```

</TabItem>
<TabItem value="uikit" label="UIKit">

Implémentez la méthode sur votre `AdaptyFlowControllerDelegate` :

```swift showLineNumbers title="Swift"
func flowController(
    _ controller: AdaptyFlowController,
    didReceiveAnalyticEvent name: String,
    params: [String: any Sendable]
) {
    handleFlowInput(name: name, params: params)
}
```

</TabItem>
</Tabs>

La closure et la méthode du délégué reçoivent les deux mêmes arguments, le code qui lit la valeur est donc identique dans les deux cas.

Le callback `didReceiveAnalyticEvent` transmet tous les événements analytiques d'un flow, y compris les [vues d'écran](ios-flow-screen-views).
- Le paramètre `name` contient le nom de l'événement. Pour filtrer les événements de saisie utilisateur, comparez `name` à `flow_user_input`.
- Le paramètre `element_type` indique la catégorie de l'élément.
- La valeur saisie est stockée dans différents paramètres selon le type d'élément :
     - Les champs texte, les sélecteurs et les bascules stockent la saisie de l'utilisateur dans `value`
     - Les groupes sélectionnables indiquent les options actives dans `item_ids` et `item_titles`

```swift showLineNumbers title="Swift"
func handleFlowInput(name: String, params: [String: any Sendable]) {
    guard name == "flow_user_input",
          let elementId = params["element_id"] as? String,
          let elementType = params["element_type"] as? String
    else { return }

    // The screen the input sits on. Pair it with elementId to tell apart
    // two fields that share an Element ID on different screens.
    let screenId = params["instanceId"] as? String

    switch elementType {
    case "text_input", "email_input", "number_input", "phone_input":
        let text = params["value"] as? String
    case "date_picker", "time_picker", "date_time_picker":
        // Integer Unix time in milliseconds, not the seconds Date expects.
        let date = (params["value"] as? Int).map { Date(timeIntervalSince1970: Double($0) / 1000) }
    case "single_choice":
        let optionId = (params["item_ids"] as? [String])?.first
    case "multi_choice":
        let optionIds = params["item_ids"] as? [String]
    case "toggle":
        let isOn = params["value"] as? Bool
    default:
        break
    }
}
```

Pour confirmer que le callback se déclenche, interagissez avec le champ de saisie dans une version de test de votre application. Si votre gestionnaire de callback ne reçoit pas d'événement, vérifiez les [prérequis](#before-you-start). Assurez-vous que le flow a bien été publié après que cette fonctionnalité est devenue disponible.

## Quand votre application reçoit l'entrée \{#when-your-app-receives-the-input\}

:::important
La valeur saisie ou sélectionnée par défaut n'est jamais transmise à votre application via ce callback. Si un utilisateur accepte l'option marquée **Set as default** et continue, aucun événement ne se déclenche. Ne considérez pas l'absence d'événement comme « aucune réponse » — l'utilisateur a simplement conservé la valeur par défaut.
:::

**Les éléments suivants déclenchent cet événement :**

- Champs texte, e-mail, numérique et téléphone
- Sélecteurs de date, d'heure et de date-heure
- Groupes sélectionnables à choix unique et à choix multiple, et bascules

**Les éléments suivants ne le déclenchent pas :**

- Champs de mot de passe, sélections de produits et changements d'onglet
- Les saisies à l'intérieur d'un élément Header, partagé entre les écrans
- Les groupes sélectionnables avec des identifiants d'élément dupliqués ou manquants, ou dont l'identifiant de groupe est réutilisé sur un autre écran. Un tel groupe n'envoie rien du tout, plutôt qu'une réponse partielle.

**L'événement se déclenche lorsque :**

- Un champ perd le focus. Un champ vidé signale une chaîne vide ; un champ que l'utilisateur n'a jamais modifié ne signale rien. Un espace réservé n'est pas une valeur. Si l'utilisateur revient sur le champ, le modifie et le quitte à nouveau, un second événement est émis.
- L'utilisateur ferme un sélecteur après avoir choisi une nouvelle valeur. Le fermer sans modification ne signale rien.
- L'utilisateur appuie sur une option ou une bascule. Un événement à choix multiple liste toutes les options sélectionnées ; désélectionner la dernière envoie donc deux tableaux vides.

**L'événement ne se déclenche pas lorsque :**

- L'utilisateur saisit du texte. Il n'y a pas de flux de frappes clavier, seulement la valeur que contient le champ au moment où il perd le focus.
- L'utilisateur soumet ou ferme le flow. Une valeur encore en cours de saisie à cet instant peut être perdue ; la section [limites de livraison](#delivery-and-limitations) explique comment concevoir le dernier écran en tenant compte de cela.
- Une valeur est définie sans interaction de l'utilisateur. Une option marquée **Set as default** est présélectionnée à l'ouverture de l'écran, et une action **Set Variable** peut sélectionner une option ou remplir un champ depuis une autre interaction. Aucune des deux n'envoie d'événement ; une saisie préremplie n'est signalée que lorsque l'utilisateur la modifie.

## Ce que vous recevez \{#what-you-receive\}

Le callback transmet deux événements. Filtrez par `name` pour n'afficher que les événements `flow_user_input`. La charge utile JSON de la réponse ressemble à ceci :

```json
{
  "name": "flow_user_input",
  "instanceId": "scr_registration",
  "isBackendEvent": false,
  "isCustomerEvent": true,
  "element_id": "email",
  "element_type": "email_input",
  "value": "jane@example.com"
}
```

| Paramètre         | Description |
|:------------------|:------------|
| `name`            | `flow_user_input` pour les événements de saisie, `flow_screen_showed` pour les affichages d'écran. |
| `instanceId`      | L'ID de l'écran contenant le champ de saisie. Les IDs d'éléments sont uniques au sein d'un écran, pas dans l'ensemble du flow. Si votre flow comporte des champs de saisie sur plusieurs écrans, combinez `instanceId` avec `element_id` pour filtrer les événements. |
| `element_id`      | L'**Element ID** du [champ de saisie](builder-inputs-and-forms), ou le **Group ID** du [groupe sélectionnable](flow-selectable-elements). |
| `element_type`    | Le type d'élément qui a envoyé l'événement. Il détermine lequel des paramètres ci-dessous contient la valeur saisie. |
| `value`           | **Champs texte, sélecteurs et bascules uniquement.** La valeur saisie : une chaîne pour les champs texte, un entier pour les sélecteurs, un booléen pour les bascules. |
| `item_ids`        | **Groupes sélectionnables à choix unique et à choix multiple uniquement.** Les **Element IDs** des options sélectionnées, dans l'ordre d'apparition des options dans le builder. Une entrée pour un groupe à choix unique ; un nombre quelconque pour un groupe à choix multiple. |
| `item_titles`     | **Groupes sélectionnables à choix unique et à choix multiple uniquement.** Les titres des options listées dans `item_ids`, dans le même ordre. Jamais vide : une option sans titre renvoie son ID à la place. |
| `isCustomerEvent` | Indicateur utilitaire, toujours `true` pour cet événement. Il identifie les événements que le SDK transmet à votre callback. Utile si un seul handler transfère tous les événements du flow vers vos analytics et que vous filtrez sur cet indicateur plutôt que sur `name`. |
| `isBackendEvent`  | Indicateur utilitaire, toujours `false` pour cet événement. Il identifie les événements qu'Adapty enregistre également pour ses propres analytics. `false` confirme que ce que les utilisateurs saisissent ne parvient qu'à votre application — Adapty ne le reçoit pas et ne le stocke pas. |

Ce que chaque élément remonte :

| Dans le builder | `element_type` | Paramètre contenant la valeur | Ce qu'il contient |
|:----------------|:---------------|:------------------------------|:------------------|
| Champ **Texte**, **Nombre**, **Numéro de téléphone** | `text_input`, `number_input`, `phone_input` | `value` | La chaîne brute saisie par l'utilisateur. Les nombres arrivent sous forme de chaînes, et non de types numériques. |
| Champ **E-mail** | `email_input` | `value` | La chaîne brute saisie par l'utilisateur, même si elle n'a pas passé la validation de format du builder. Validez-la de votre côté avant de l'utiliser. |
| Champ **Mot de passe** | aucun | aucun | N'envoie aucun événement. |
| Champ **Date** | `date_picker` | `value` | Horodatage Unix en millisecondes, sous forme d'entier, à minuit heure locale de la date sélectionnée. |
| Champ **Heure** | `time_picker` | `value` | Horodatage Unix en millisecondes, sous forme d'entier, arrondi à la minute inférieure. |
| Champ **Date et heure** | `date_picker` et `time_picker` | `value` | Deux éléments : un sélecteur de date et un sélecteur d'heure. Chacun envoie son propre événement. |
| Champ passé en mode **Date et heure** dans le menu déroulant **Type** | `date_time_picker` | `value` | Horodatage Unix en millisecondes, sous forme d'entier, arrondi à la minute inférieure. |
| Groupe à **choix unique** | `single_choice` | `item_ids`, `item_titles` | Deux tableaux. `item_ids` : un tableau contenant l'Element ID de l'option sélectionnée. `item_titles` : un tableau contenant le titre de cette option. |
| Groupe à **choix multiple** | `multi_choice` | `item_ids`, `item_titles` | Deux tableaux. `item_ids` : les Element IDs de toutes les options sélectionnées, dans l'ordre d'apparition dans le builder. `item_titles` : leurs titres, dans le même ordre. Les deux tableaux sont vides si rien n'est sélectionné. |
| Groupe **Bascule** | `toggle` | `value` | Un booléen. |

Pour brancher votre logique selon une réponse, comparez `item_ids`, pas `item_titles`. Un titre est dérivé : l'**Element Title** de l'option si vous en avez défini un, sinon son texte dans votre langue par défaut, sinon son Element ID. Un utilisateur ayant consulté le flow dans une autre langue a vu un texte différent.

## Exemples d'événements \{#event-examples\}

Ces exemples montrent les propriétés disponibles pour chaque événement, avec des valeurs illustratives en commentaires.

<Details>
<summary>Text, email, number, and phone input (Click to expand)</summary>

```swift
func handleFlowInput(name: String, params: [String: any Sendable]) {
    name;                      // "flow_user_input"
    params["name"];            // "flow_user_input"
    params["instanceId"];      // "scr_J260KU5q"
    params["isCustomerEvent"]; // true
    params["isBackendEvent"];  // false
    params["element_id"];      // "email"
    params["element_type"];    // "email_input"
    params["value"];           // "jane@example.com"   (String)
}
```
</Details>

<Details>
<summary>Sélecteurs de date, d'heure et de date-heure (Cliquez pour développer)</summary>

```swift
func handleFlowInput(name: String, params: [String: any Sendable]) {
    params["element_id"];      // "birthday"
    params["element_type"];    // "date_picker"
    params["value"];           // 645408000000   (Unix milliseconds — 1990-06-15, local midnight)
}
```
</Details>

<Details>
<summary>Choix unique (Cliquez pour développer)</summary>

```swift
func handleFlowInput(name: String, params: [String: any Sendable]) {
    params["element_id"];      // "experience"
    params["element_type"];    // "single_choice"
    params["item_ids"];        // ["pro"]
    params["item_titles"];     // ["I train professionally"]
}
```
</Details>

<Details>
<summary>Multi choice (Click to expand)</summary>

```swift
func handleFlowInput(name: String, params: [String: any Sendable]) {
    params["element_id"];      // "interests"
    params["element_type"];    // "multi_choice"
    params["item_ids"];        // ["sports", "music"]
    params["item_titles"];     // ["Sports", "Music"]
}
```
</Details>

<Details>
<summary>Toggle (Click to expand)</summary>

```swift
func handleFlowInput(name: String, params: [String: any Sendable]) {
    params["element_id"];      // "reminders"
    params["element_type"];    // "toggle"
    params["value"];           // true   (Bool)
}
```
</Details>

## Livraison et limitations \{#delivery-and-limitations\}

:::warning
Les flows transmettent les valeurs brutes — adresses e-mail, numéros de téléphone et tout ce qu'un utilisateur saisit. Traitez tout ce que le callback analytics vous fournit comme des données personnelles, et ne les écrivez nulle part où vous n'écririez pas l'adresse e-mail d'un utilisateur.
:::

- **La dernière valeur gagne** : Vous recevez un événement par champ, portant la valeur sur laquelle l'utilisateur s'est arrêté plutôt qu'un flux touche par touche. S'il modifie un champ, seule la dernière version vous parvient.
- **Aucune garantie d'envoi** : Les valeurs vous parviennent au fur et à mesure que les utilisateurs avancent dans le flow, et un utilisateur peut partir à tout moment. Attendez que le flow se ferme avant de considérer un ensemble de réponses comme complet.
- **Distribution au mieux** : Si un utilisateur ferme le flow ou réduit l'application pendant qu'un champ est encore actif ou qu'un sélecteur est encore ouvert, cette valeur peut être perdue.

Pour que la valeur du dernier champ soit fiable, terminez le flow par un écran sans champs de saisie ni sélecteurs, et fermez le flow depuis une action explicite de l'utilisateur plutôt qu'automatiquement à l'apparition de cet écran. Passer à l'écran final retire le focus du champ précédent, ce qui déclenche l'envoi de sa valeur.

Stockez chaque valeur d'entrée au fur et à mesure que votre handler la reçoit, puis envoyez l'ensemble complet lorsque le flow se ferme. Pour intercepter ce moment, implémentez `flowControllerDidDisappear` sur votre `AdaptyFlowControllerDelegate` en UIKit, ou passez une closure `didDisappear` au modificateur `.flow` en SwiftUI. Les deux s'exécutent une fois que la vue du flow a quitté l'écran, que l'utilisateur ait terminé le flow ou qu'il l'ait simplement fermé.

## Cas d'usage \{#use-cases\}

### Enregistrer les utilisateurs sur votre backend \{#register-users-on-your-backend\}

Collectez les valeurs au fur et à mesure qu'elles arrivent et envoyez-les une fois le flow fermé, afin qu'une seule requête contienne un ensemble complet de réponses.

La vue disparaît que l'utilisateur ait terminé le flow ou qu'il l'ait abandonné en cours de route. Vérifiez que les champs dont vous avez besoin sont bien renseignés avant d'appeler votre backend.

Le flow ne peut pas afficher les erreurs renvoyées par votre backend. Le callback n'a pas de valeur de retour, et le SDK ne dispose d'aucune méthode pour envoyer des données dans un flow en cours d'exécution. Si l'inscription échoue, par exemple parce que l'adresse e-mail est déjà utilisée, affichez l'erreur dans votre propre interface après la fermeture du flow.

```swift showLineNumbers title="Swift"
private var flowAnswers: [String: String] = [:]

func handleFlowInput(name: String, params: [String: any Sendable]) {
    guard name == "flow_user_input",
          let elementId = params["element_id"] as? String,
          let value = params["value"] as? String
    else { return }

    flowAnswers[elementId] = value
}

func flowControllerDidDisappear(_ controller: AdaptyFlowController) {
    guard flowAnswers["email"] != nil else { return }

    // Send flowAnswers to your backend here to create the account.

    flowAnswers.removeAll()
}
```

### Enrichir les profils utilisateurs avec des données \{#enrich-user-profiles-with-data\}

Pour relier ce qu'un utilisateur a saisi à son profil et éviter de lui redemander les mêmes informations, [mettez à jour le profil utilisateur](setting-user-attributes) au fur et à mesure que les valeurs arrivent.

Par exemple, si votre flow comporte un champ texte avec l'ID d'élément `name` et un champ e-mail avec l'ID d'élément `email` :

```swift showLineNumbers title="Swift"
func handleFlowInput(name: String, params: [String: any Sendable]) {
    guard name == "flow_user_input",
          let elementId = params["element_id"] as? String,
          let value = params["value"] as? String
    else { return }

    let builder = AdaptyProfileParameters.Builder()

    switch elementId {
    case "name":
        builder.with(firstName: value)
    case "email":
        builder.with(email: value)
    default:
        return
    }

    // Delegate methods are synchronous; kick off the async update in a Task.
    Task {
        do {
            try await Adapty.updateProfile(params: builder.build())
        } catch {
            // handle the error
        }
    }
}
```

### Personnaliser les flows affichés ultérieurement \{#customize-flows-shown-later\}

Les réponses à un quiz peuvent également déterminer ce qu'un utilisateur voit à un [placement](placements) ultérieur — un flow différent, ou un paywall différent à l'intérieur de celui-ci.

Par exemple, demandez aux utilisateurs leur expérience sportive dans votre flow d'onboarding, puis montrez à chaque groupe son propre flow avec des produits et des textes différents.

1. Ajoutez un [quiz](onboarding-quizzes) à votre flow. Donnez au [groupe sélectionnable](flow-selectable-elements) le Group ID `experience`, et à chaque option un Element ID significatif.
2. Gérez les réponses et [définissez des attributs personnalisés](setting-user-attributes) pour l'utilisateur.

```swift showLineNumbers title="Swift"
func handleFlowInput(name: String, params: [String: any Sendable]) {
    guard name == "flow_user_input",
          params["element_id"] as? String == "experience",
          let optionId = (params["item_ids"] as? [String])?.first
    else { return }

    let builder = AdaptyProfileParameters.Builder()
    // Set the custom attribute 'experience' to the option the user selected
    // (beginner, amateur, or pro).
    try? builder.with(customAttribute: optionId, forKey: "experience")

    Task {
        do {
            try await Adapty.updateProfile(params: builder.build())
        } catch {
            // handle the error
        }
    }
}
```

3. [Créez un segment](segments) pour chaque valeur d'attribut personnalisé.
4. Créez un [placement](placements) et ajoutez une [audience](audience) pour chaque segment.
5. [Affichez le flow](ios-present-paywalls) correspondant à ce placement dans votre application.