Growth
App Store Optimization

iOS Home Screen Quick Actions : intercepter vos utilisateurs avant qu'ils suppriment votre app

Un utilisateur qui supprime votre app n'a plus rien à perdre, il vous dit la vérité. Voici comment l'intercepter avant qu'il parte, avec le code complet en React Native et Swift.

Résumé l'article avec

La plupart des fondateurs regardent leurs utilisateurs partir sans rien faire. Ils voient le churn dans leurs dashboards, mais ils n'ont jamais la conversation qui expliquerait pourquoi.

Il existe une mécanique native iOS, quasi-ignorée, qui vous permet d'intercepter un utilisateur au moment exact où il s'apprête à supprimer votre app. Pas une popup intrusive. Pas un email de relance envoyé trois jours trop tard. Une conversation au bon endroit, au bon moment.

Voici comment l'implémenter, en React Native et en Swift natif. C'est le genre de mécanique que notre agence de création d'application mobile firstapp intègre pour transformer le churn en signal produit.

Ce que sont les Home Screen Quick Actions

Depuis iOS 13, quand un utilisateur appuie longuement sur l'icône d'une app, un menu contextuel apparaît. Par défaut, iOS affiche "Partager l'app" et "Supprimer l'app".

Ce menu est entièrement personnalisable. Vous pouvez y ajouter jusqu'à 4 actions propres à votre app, des raccourcis qui s'ouvrent directement sur une fonctionnalité, un écran, ou dans notre cas, un formulaire.

L'idée est simple : vous placez une action intitulée "Un problème ? Dites-nous." dans ce menu. L'utilisateur qui s'apprête à supprimer l'app tombe dessus. Une partie d'entre eux clique. Et vous récupérez le feedback le plus honnête qui soit, celui d'un utilisateur qui n'a plus rien à perdre.

Exemple d'un iOS Home Screnn Quick Actions

Pourquoi ce feedback est différent

Un utilisateur qui remplit un survey in-app est encore là. Il a encore de la bienveillance envers votre produit. Il va souvent vous dire ce qu'il pense que vous voulez entendre, ou cocher la réponse la plus rapide.

Un utilisateur qui supprime votre app a pris sa décision. Il n'a aucune raison de vous mentir. Il va vous dire la vraie raison, la friction qu'il n'a pas mentionnée, la fonctionnalité qu'il attendait, le bug qui l'a fait partir.

C'est le feedback que vous ne trouverez nulle part ailleurs

Implémentation en React Native (Expo)

Installation

La solution la plus simple pour un projet Expo est expo-quick-actions, maintenue par Evan Bacon (core team Expo).

npx expo install expo-quick-actions

Définir l'action dans le menu

Ajoutez les Quick Actions au démarrage de l'app, dans votre layout racine ou votre App.tsx :

import * as QuickActions from "expo-quick-actions";
import { useEffect } from "react";

export default function RootLayout() {
  useEffect(() => {
    QuickActions.setItems([
      {
        id: "churn-feedback",
        title: "Un problème ? Dites-nous.",
        subtitle: "30 secondes. Ça compte vraiment.",
        icon: "bubble.left", // SF Symbol iOS
      },
    ]);
  }, []);

  // ...
}
Note : Sur Android, l'équivalent s'appelle les App Shortcuts. La même librairie les gère, le paramètre icon accepte un drawable Android.

Écouter le tap sur l'action

Quand l'utilisateur tape sur votre Quick Action, l'app s'ouvre et vous recevez un event. Interceptez-le pour rediriger vers votre formulaire :

import * as QuickActions from "expo-quick-actions";
import { useRouter } from "expo-router";
import { useEffect } from "react";

export default function RootLayout() {
  const router = useRouter();

  useEffect(() => {
    // Cas où l'app est déjà ouverte en arrière-plan
    const subscription = QuickActions.addListener((action) => {
      if (action.id === "churn-feedback") {
        router.push("/feedback");
      }
    });

    return () => subscription.remove();
  }, []);

  useEffect(() => {
    // Cas où l'app est lancée depuis la Quick Action (cold start)
    QuickActions.getInitialAction().then((action) => {
      if (action?.id === "churn-feedback") {
        router.push("/feedback");
      }
    });
  }, []);

  // ...
}

Le formulaire de feedback

Gardez-le court. Trois questions suffisent :

// app/feedback.tsx
import { useState } from "react";
import { View, Text, TextInput, TouchableOpacity, StyleSheet } from "react-native";

const REASONS = [
  "L'app ne répondait pas à mon besoin",
  "Trop cher",
  "J'ai trouvé une meilleure alternative",
  "Problème technique",
  "Je l'utilise plus assez",
  "Autre",
];

export default function FeedbackScreen() {
  const [source, setSource] = useState("");
  const [reason, setReason] = useState("");
  const [suggestion, setSuggestion] = useState("");
  const [submitted, setSubmitted] = useState(false);

  const handleSubmit = async () => {
    await fetch("https://votre-api.com/churn-feedback", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ source, reason, suggestion }),
    });
    setSubmitted(true);
  };

  if (submitted) {
    return (
      <View style={styles.container}>
        <Text style={styles.title}>Merci — c'est noté.</Text>
        <Text style={styles.subtitle}>
          Votre retour va directement dans notre backlog produit.
        </Text>
      </View>
    );
  }

  return (
    <View style={styles.container}>
      <Text style={styles.title}>Avant de partir</Text>
      <Text style={styles.label}>Comment avez-vous découvert l'app ?</Text>
      <TextInput
        style={styles.input}
        value={source}
        onChangeText={setSource}
        placeholder="App Store, bouche-à-oreille, réseaux..."
      />

      <Text style={styles.label}>Pourquoi vous partez ?</Text>
      {REASONS.map((r) => (
        <TouchableOpacity
          key={r}
          style={[styles.option, reason === r && styles.optionSelected]}
          onPress={() => setReason(r)}
        >
          <Text>{r}</Text>
        </TouchableOpacity>
      ))}

      <Text style={styles.label}>Qu'est-ce qu'on aurait pu faire mieux ?</Text>
      <TextInput
        style={[styles.input, styles.textarea]}
        value={suggestion}
        onChangeText={setSuggestion}
        placeholder="Répondez librement..."
        multiline
        numberOfLines={4}
      />

      <TouchableOpacity style={styles.button} onPress={handleSubmit}>
        <Text style={styles.buttonText}>Envoyer</Text>
      </TouchableOpacity>
    </View>
  );
}

const styles = StyleSheet.create({
  container: { flex: 1, padding: 24, backgroundColor: "#fff" },
  title: { fontSize: 22, fontWeight: "700", marginBottom: 8 },
  subtitle: { fontSize: 15, color: "#666", marginTop: 8 },
  label: { fontSize: 15, fontWeight: "600", marginTop: 20, marginBottom: 8 },
  input: { borderWidth: 1, borderColor: "#ddd", borderRadius: 8, padding: 12 },
  textarea: { height: 100, textAlignVertical: "top" },
  option: { padding: 12, borderRadius: 8, borderWidth: 1, borderColor: "#ddd", marginBottom: 8 },
  optionSelected: { borderColor: "#000", backgroundColor: "#f5f5f5" },
  button: { backgroundColor: "#000", padding: 16, borderRadius: 8, marginTop: 24, alignItems: "center" },
  buttonText: { color: "#fff", fontWeight: "700", fontSize: 16 },
});

Implémentation en Swift natif

Pour une app Swift/UIKit, les Quick Actions se configurent dans l'AppDelegate :

// AppDelegate.swift

func application(
  _ application: UIApplication,
  didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {

  // Définir la Quick Action
  let feedbackAction = UIApplicationShortcutItem(
    type: "com.votreapp.churn-feedback",
    localizedTitle: "Un problème ? Dites-nous.",
    localizedSubtitle: "30 secondes. Ça compte vraiment.",
    icon: UIApplicationShortcutIcon(systemImageName: "bubble.left"),
    userInfo: nil
  )
  application.shortcutItems = [feedbackAction]

  // Gérer le cas cold start
  if let shortcutItem = launchOptions?[.shortcutItem] as? UIApplicationShortcutItem {
    handleShortcut(shortcutItem)
  }

  return true
}

// Gérer le tap quand l'app est déjà ouverte
func application(
  _ application: UIApplication,
  performActionFor shortcutItem: UIApplicationShortcutItem,
  completionHandler: @escaping (Bool) -> Void
) {
  handleShortcut(shortcutItem)
  completionHandler(true)
}

private func handleShortcut(_ shortcutItem: UIApplicationShortcutItem) {
  if shortcutItem.type == "com.votreapp.churn-feedback" {
    // Ouvrir le FeedbackViewController
    NotificationCenter.default.post(name: .openFeedback, object: nil)
  }
}

Pour SwiftUI, utilisez le modifier .onOpenURL ou passez par un @EnvironmentObject pour déclencher la navigation depuis l'action.

Le wording qui fait la différence

Le titre de votre Quick Action est ce que l'utilisateur lit en premier. C'est votre seule chance.

À éviter :

  • "Donner un avis" → trop administratif
  • "Nous contacter" → perçu comme du support
  • "Feedback" → trop vague, trop anglophone

Ce qui fonctionne :

  • "Un problème ? Dites-nous." → empathie + invitation
  • "Avant de partir, 30 secondes ?" → crée une pause dans le geste
  • "Quelque chose ne va pas ?" → ouvre la conversation

La limite iOS est de ~35 caractères pour le titre et ~45 pour le sous-titre. Testez plusieurs formulations.

Ce que vous faites des réponses

Collecter du feedback sans process pour le traiter ne sert à rien. Voici un setup minimal :

  1. Envoyez les réponses dans un canal Slack dédié, chaque feedback arrive en temps réel, visible de toute l'équipe
  2. Taguez par raison principale, vous verrez très vite les patterns émerger
  3. Relisez toutes les semaines, pas une fois par trimestre
  4. Agissez sur les quick wins, si 40% des réponses mentionnent le même bug, c'est votre sprint suivant

Un utilisateur qui prend 30 secondes pour vous expliquer pourquoi il part mérite une correction dans votre roadmap. C'est le contrat implicite de cette mécanique.

Limites à connaître

  • Maximum 4 Quick Actions par app, choisissez bien laquelle consacrer au feedback
  • Android fonctionne différemment, les App Shortcuts Android apparaissent aussi sur un appui long, mais le comportement peut varier selon les launchers
  • L'action n'intercepte pas la suppression, elle apparaît dans le même menu, mais l'utilisateur peut toujours supprimer directement sans la voir. Ce n'est pas un blocage, c'est une invitation
  • L'app doit avoir été lancée au moins une fois pour que les Quick Actions dynamiques apparaissent

Vous avez mis en place cette mécanique et vous voulez savoir ce que le reste de votre app dit à vos utilisateurs avant qu'ils partent ? On audite votre onboarding, votre paywall et vos boucles de feedback, et on vous dit exactement ce qui fait churner. Demander un audit de mon app !

Restez Informé(e) !

Inscrivez-vous à notre newsletter pour ne rien manquer de l'actualité firstapp.

Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.
Motif en dégradé de pixels noirs et blancs, avec une concentration élevée de pixels noirs sur le côté gauche qui se dispersent vers la droite.