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.

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-actionsDé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 :
- Envoyez les réponses dans un canal Slack dédié, chaque feedback arrive en temps réel, visible de toute l'équipe
- Taguez par raison principale, vous verrez très vite les patterns émerger
- Relisez toutes les semaines, pas une fois par trimestre
- 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 !





