J'ai mené deux apps iOS jusqu'à la review d'Apple cet automne : Stakko, un jeu de blocs écrit avec Unity, et Soirama, une app Expo qui recommande un film, une série ou un animé selon l'humeur du soir. Les deux passent par le même compte développeur, utilisent un identifiant d'app au format fr.kconsulting.<app>, intègrent des publicités AdMob et proposent un achat intégré « sans pub ». La review m'a renvoyé trois retours. Aucun ne remettait en cause le concept des apps. Voici ce qui s'est passé, et ce que j'ai mis en place pour que ça ne se reproduise pas.
Avant le premier build : ce qui ne se rattrape pas
Certaines erreurs ne déclenchent pas de refus de la review. Elles bloquent plus tôt, et en silence.
- La version du build doit être celle de la fiche. App Store Connect crée une version « 1.0 ». Un build qui porte
1.0.0ne s'y rattache jamais. - Le numéro de build est strictement croissant, et Apple refuse définitivement un numéro déjà reçu, même si le build a été supprimé. J'utilise le format
AAAAMMJJNN, calculé par le script de release et commité après chaque envoi. - L'icône 1024 ne doit pas avoir de canal alpha. Celle de Stakko en avait un, avec une opacité de 230 sur les rayons : elle a été aplatie en RGB avant le premier envoi.
- Le chiffrement se déclare une fois. Les deux apps n'utilisent que HTTPS :
ITSAppUsesNonExemptEncryption = NOévite que la question de conformité export revienne à chaque build.
Le script de Soirama vérifie ces deux derniers points dans l'Info.plist généré, avant même d'archiver :
today="$(date +%Y%m%d)"
last="$(node -p "require('$ROOT/app.json').expo.ios.buildNumber ?? '0'")"
if [ "${last:0:8}" = "$today" ]; then
# 10# force la base décimale : sans lui « 08 » serait lu en octal.
BUILD_NUMBER="$today$(printf '%02d' $(( 10#${last:8} + 1 )))"
else
BUILD_NUMBER="${today}01"
fi
# ...
[ "$(/usr/libexec/PlistBuddy -c 'Print :ITSAppUsesNonExemptEncryption' "$plist" 2>/dev/null)" = "false" ] \
|| die "ITSAppUsesNonExemptEncryption absent de $plist"Pour Stakko, j'ai aussi fait un audit avant la première soumission. Il a corrigé plusieurs motifs de refus classiques : « Restaurer les achats » n'apparaissait qu'une fois l'achat connu alors qu'il doit toujours être visible (règle 3.1.1), il manquait un lien vers la politique de confidentialité dans l'app (règle 5.1.1), et Game Center était annoncé sur la fiche alors qu'il était absent du build.
Retour n°1 : le mot « Android » (règle 2.3.10)
Stakko a été refusé le 23 septembre pour une ligne de sa description : « SUR IPHONE, IPAD ET TABLETTES ANDROID ». La description App Store ne doit citer ni Android ni Google Play, même quand le texte est partagé avec la fiche Play. La ligne est devenue « SUR TÉLÉPHONE ET TABLETTE ». La règle est maintenant écrite en tête des fichiers de fiche, pour que la prochaine réécriture ne la réintroduise pas.
Comme le correctif ne touchait que les métadonnées, il n'y a pas eu de nouveau build. J'ai modifié la description par l'API App Store Connect (PATCH /v1/appStoreVersionLocalizations/{id}). Le renvoi, lui, n'est pas passé par l'API : resoumettre la soumission marquée « problèmes non résolus » répondait 409, et le bouton « Soumettre de nouveau » de l'interface a été le chemin le plus court.
Retour n°2 : « Information Needed » (règle 2.1)
Pour un compte qui a peu d'historique, ce retour arrive presque systématiquement à la première soumission. Ce n'est pas un refus sur le fond : Apple demande six choses.
- Un enregistrement d'écran sur un vrai appareil, qui commence au lancement et montre le parcours type ainsi que l'accès au contenu payant.
- Le but de l'app et son public.
- Comment accéder aux fonctions, avec des identifiants s'il en faut.
- Les services externes : données, IA, publicité, paiement.
- Les différences selon les régions.
- Les droits sur le contenu tiers.
La réponse de Soirama suit ces six points, en anglais et sans adjectifs. Elle précise ce qui a été filmé et sur quoi : l'iPhone pour le parcours principal, et le simulateur pour l'écran d'achat, en le disant. Elle liste chaque service : le catalogue TMDB et AniList, le modèle embarqué d'Apple ou un modèle ouvert téléchargeable, AdMob avec le formulaire de consentement, StoreKit. Pour le contenu tiers, la règle est de décrire la source et l'attribution telles qu'elles sont, et de ne jamais affirmer une licence qu'on n'a pas. Pour la suite, ces six points sont rédigés dans les notes de revue dès la première soumission, et la vidéo est jointe à la version. Cela évite un aller-retour.
Retour n°3 : un crash au lancement, sur un iPad
Soirama était une app iPhone uniquement. App Review l'a pourtant lancée sur un iPad Air sous iPadOS 27, en mode compatibilité, et elle a planté au démarrage. Une app compilée avec le SDK iOS 27 doit adopter le cycle de vie par scènes d'UIKit. Expo 57 fournit déjà le délégué de scène, mais son modèle de projet démarre encore React Native depuis l'app delegate. Un plugin de configuration fait la bascule au prebuild :
// iOS 27 refuses to launch an app built with the iOS 27 SDK unless it adopts
// the UIKit scene life cycle. Expo SDK 58 does this in its template;
// drop the plugin when upgrading.
plist.modResults.UIApplicationSceneManifest = {
UIApplicationSupportsMultipleScenes: false,
UISceneConfigurations: {
UIWindowSceneSessionRoleApplication: [{
UISceneConfigurationName: 'Default Configuration',
UISceneDelegateClassName: 'EXExpoAppSceneDelegate',
}],
},
};J'en ai tiré une conclusion plus large : même une app iPhone tourne sur iPad pendant la review. Soirama a gagné une vraie mise en page iPad. Comme elle déclare maintenant la tablette, elle doit fournir des captures iPad 13 pouces, et ces captures doivent venir du même build que celui soumis. Toute retouche d'interface après l'envoi impose donc un nouveau build.
Captures et confidentialité : rien de faux
Le format obligatoire pour l'iPhone est le 6,9 pouces, en 1320 × 2868. Le simulateur iPhone 17 Pro Max produit nativement cette taille, alors que le 17 Pro, un 6,3 pouces, rend du 1206 × 2622 et ne convient pas. Les deux apps ont un mode dédié pour produire des captures reproductibles. Stakko accepte un argument de lancement qui choisit la scène, le thème et la langue. Soirama a un mode QA qui charge des données réalistes.
La confidentialité demande la même honnêteté que la description. Avec AdMob, une app ne collecte plus « aucune donnée ». Les étiquettes App Privacy déclarent ce que le SDK collecte : identifiant de l'appareil, données publicitaires, interactions, localisation approximative et diagnostics. Ces données ne sont pas liées à l'identité, mais elles servent au suivi. Cette déclaration doit être cohérente avec le reste : le texte NSUserTrackingUsageDescription qui déclenche la demande ATT, le formulaire de consentement UMP présenté avant toute annonce, et la description qui ne dit jamais « sans pub ». Le questionnaire de classification d'âge se remplit pour le contenu réellement affiché : affiches et résumés de films de tous genres, et publicité déclarée. Soirama est classée 13+.
Un dernier piège ne vient pas d'Apple. Un build personnel installé sur mon propre iPhone sert toujours les publicités de test. Toucher ses propres annonces réelles fait suspendre le compte AdMob.
Ce que j'en retiens
- Les métadonnées se relisent comme du code. Un seul mot, « Android », a suffi à faire refuser une version.
- Une demande d'informations s'anticipe. Les six points et une vidéo sur un vrai appareil, dès la première soumission.
- Une app iPhone est aussi testée sur iPad, avec le dernier système. Il faut la lancer au moins une fois sur un simulateur iPad récent.
- La fiche, les étiquettes de confidentialité et l'app doivent raconter la même chose. Une incohérence entre publicité, suivi et description est un motif de refus en soi.
Vous avez une app à faire passer en review, ou un refus que vous ne comprenez pas ? Envoyez-moi le message d'Apple, on le lit ensemble.