Expo sur iPhone Duo
Source : https://expo.dev/blog/supporting-iphone-duo (Expo SDK 58). Les règles de conception se trouvent dans apple-design-duo ; ce skill constitue la couche d'implémentation Expo et React Native. Contexte du pipeline CI : https://docs.expo.dev/tutorial/cicd/introduction/ et le skill expo:eas-workflows.
Prérequis
- Expo SDK 58 (cible iOS 27) et Xcode 27.1 (runtime iOS 27.1 et simulateur iPhone Duo).
- Image EAS Build :
sdk-58oulatest. - Xcode 27 remplace Simulator.app par Device Hub (Xcode > Open Developer Tool > Device Hub). Il dispose d'un Duo 3D qui se plie, tourne et définit l'angle de la charnière.
Modèle mental
Plier = largeur compacte, ouvrir = largeur normale. Laissez les mises en page s'élargir, ne changez jamais de conception. Mêmes fonctionnalités et état des deux côtés. Dans les poses livre, iOS déplace la barre d'état, la barre d'onglets et la barre d'outils vers une barre verticale sur un bord.
Les quatre règles
- Laissez iOS gérer la navigation. Utilisez
Stackd'Expo Router etNativeTabs. Les en-têtes JS personnalisés et<TabList>ne sont pas adaptés à la barre verticale (SDK 58 n'ajoute qu'une compatibilité minimale) et<TabList>ne peut pas aller dans la barre. SafeAreaView(react-native-safe-area-context) sur chaque écran, encarts horizontaux inclus.- Testez chaque pose. Plier, déplier, pivoter, Split View.
- Ajustez uniquement là où nécessaire :
ArrangementView,preferredPlacement, régions réservées, APIs de charnière ou désactivation de la barre.
Zones sécurisées (là où se trouvent la plupart des bugs)
- La barre nécessite un encart de 84pt sur les bords gauche ou droit.
- Enveloppez le contenu défilant dans
SafeAreaView edges={['left','right']}, pas dans laScrollView, pour que les arrière-plans s'étendent d'un bord à l'autre. DéfinissezcontentInsetAdjustmentBehavior="automatic"sur la ScrollView (le patch RN #59194 pourrait rendre cela suffisant). - Carrousels horizontaux : ils ajoutent eux-mêmes le padding de fin.
- Boutons flottants et toasts :
right: insets.right + 16. En RTL, lisez à la foisleftetright. - À l'intérieur de
NativeTabs,insets.bottominclut la barre d'onglets uniquement lorsqu'elle est en bas. N'ajoutez jamais une hauteur fixe de barre d'onglets. - Remplissez le conteneur de contenu avec les encarts, pas la vue racine.
- Lisez
useWindowDimensionsau moment du rendu. La fenêtre gauche de Split View fait 469pt de large.
Expo Router
- Les boutons d'en-tête dans la barre verticale s'empilent en une colonne, avec Retour ou Fermer en premier. Les éléments textuels restent horizontaux. Les vues personnalisées sont exclues.
- Une Live Activity dans la Dynamic Island peut réduire les barres d'outils en débordement. Ne luttez pas contre cela.
<SplitView>encapsule la vue de split système : une colonne pliée, deux ouvertes, consciente du pliage. Expérimental dans SDK 58.NativeTabspreferredPlacement:'sidebar' | 'tabBar' | 'automatic'. S'applique sur l'écran ouvert en paysage.
APIs supplémentaires
| Besoin | API |
|---|---|
| Contenu à deux volets (iOS 27.1 uniquement, les versions plus anciennes rendent les enfants sans conteneur) | ArrangementView depuis @expo/ui/swift-ui, à l'intérieur d'un Host, avec ArrangementView.Primary et .Secondary. Stylisez avec arrangementViewStyle('split' | 'overlay'), splitArrangementLayoutSize, overlayArrangementEdge. Modificateurs depuis @expo/ui/swift-ui/modifiers. |
| Cadres de pliure et d'occlusion | npx expo install react-native-reserved-regions, puis useReservedRegions() retourne { kind: 'division' | 'occlusion', frame } en points. Utile pour les commandes de jeu personnalisées et les grilles à colonnes paires. Ne l'utilisez pas pour diviser le contenu au niveau du pli, utilisez ArrangementView. |
| Angle et état de la charnière | npx expo install expo-hinge. useHinge(), getHinge(), addHingeListener(). États : closed, partiallyOpen, fullyOpen, unknown. Le modificateur SwiftUI onHingeChange peut s'exécuter sur le thread UI via useWorkletCallback. Fonctionne avec Reanimated et les pliables Android 11+. |
| Bord de la barre | useVerticalBarEdgeIOS() retourne 'leading', 'trailing' ou null. Pas un détecteur de Duo (portrait ouvert retourne null). |
| Désactiver la barre | <StatusBar verticalBarBehaviorIOS="disabled" /> (mappe sur preferredVerticalBarBehavior). Uniquement pour les lecteurs vidéo ou les écrans de type calculatrice, et gardez le choix stable par écran. |
Orientation
Sous iOS 27, les verrous d'orientation sont des préférences dans des contextes redimensionnables. lockAsync peut être refusé et requireFullScreen ne permet plus de s'en affranchir. Verrouiller en portrait ou paysage sur Duo ne fonctionne que si l'application remplit l'écran via les changements de pose.
Limites connues
- StandBy n'est pas disponible dans le simulateur Duo, et la plupart des extensions d'application ne sont pas disponibles dans le simulateur de Xcode 27.1. Testez les widgets et extensions sur un vrai appareil.
expo-app-intents(alpha, également SDK 58) expose des actions à Siri, Raccourcis et Spotlight. Certains schémas nécessitent une version iOS plus récente que leur domaine (.mail.openDraftnécessite iOS 27). Sans rapport avec la mise en page.
Checklist de test
- Plié : rien d'important sous la barre, les boutons répondent.
- Ouvert : la mise en page ne change pas de conception, rien sous la barre, les barres en portrait reviennent en haut et en bas.
- Partiellement ouvert : rien d'essentiel dans le pli, les feuilles et popovers atterrissent d'un côté.
- Split View : contenu dégagé de la barre (bord gauche de la fenêtre gauche).
- Pliage en cours d'écran : la position de défilement, l'état et les actions persistent.
- iPhone standard : rien ne change.
Workflow d'audit
- Confirmez SDK 58 et reconstruisez.
- Recherchez les en-têtes ou barres d'onglets personnalisés (
headerShown: falseplus barres faites main,TabList) et déplacez-les versStacketNativeTabs. - Recherchez les décalages codés en dur
top,bottom,left,right, les hauteurs fixes de barre d'onglets et le padding au niveau racine. Remplacez par des encarts. - Vérifiez les carrousels, FABs et toasts selon la liste des zones sécurisées.
- Choisissez les écrans à deux volets :
SplitViewouArrangementView. - Décidez par écran pour les régions réservées ou la charnière uniquement si une mise en page personnalisée en a vraiment besoin.
- Vérifiez dans Device Hub, puis dans CI (ci-dessous).
CI : exécutez-le sur le simulateur Duo
Utilisez le skill expo:eas-workflows (récupérez d'abord le schéma en direct, ne devinez pas les paramètres du job). Une forme raisonnable à partir du tutoriel CI/CD : construction du simulateur déclenchée par PR sur l'image sdk-58 ou latest, Maestro E2E optionnel, puis choix basé sur l'empreinte entre une construction native et une mise à jour EAS pour la production. Validez avec npx -y eas-cli@latest workflow:validate .eas/workflows/<file>.yml --non-interactive. Les workflows consomment des minutes EAS payantes.
Erreurs courantes
| Erreur | Correction |
|---|---|
| Remplir la ScrollView avec des encarts | SafeAreaView autour d'elle, contentInsetAdjustmentBehavior="automatic" |
| Hauteur fixe de barre d'onglets dans le padding | Utilisez insets.bottom de la zone sécurisée |
Utiliser useVerticalBarEdgeIOS() pour détecter Duo | C'est uniquement le bord de la barre, null en portrait ouvert |
| Diviser le contenu au niveau du pli avec des régions réservées | ArrangementView ou SplitView |
| En-tête ou barre d'onglets JS fait main | Stack et NativeTabs |
Bouton flottant à right: 16 | right: insets.right + 16 |
Supposer que lockAsync réussit toujours | Traitez-le comme une préférence et remplissez l'écran |
| Tester uniquement sur un iPhone standard | Device Hub, chaque pose |