Expo auf iPhone Duo
Quelle: https://expo.dev/blog/supporting-iphone-duo (Expo SDK 58). Designregeln befinden sich in apple-design-duo; dieser Skill ist die Expo- und React-Native-Implementierungsebene. CI-Pipeline-Hintergrund: https://docs.expo.dev/tutorial/cicd/introduction/ und der Skill expo:eas-workflows.
Anforderungen
- Expo SDK 58 (zielt auf iOS 27) und Xcode 27.1 (iOS 27.1 Laufzeit plus iPhone Duo Simulator).
- EAS Build-Image:
sdk-58oderlatest. - Xcode 27 ersetzt Simulator.app durch Device Hub (Xcode > Open Developer Tool > Device Hub). Es gibt ein 3D-Duo, das gefaltet, gedreht und der Scharnierwinkel eingestellt werden kann.
Gedankenmodell
Gefaltet = kompakte Breite, offen = reguläre Breite. Layouts verbreitern sich, Designs werden nie umgeschaltet. Gleiche Funktionen und Zustände auf beiden Seiten. In buchartigen Posen verschiebt iOS die Statusleiste, Tab-Leiste und Symbolleiste in eine vertikale Leiste (Rail) an einer Kante.
Die vier Regeln
- iOS die Navigation zeichnen lassen. Verwende Expo Router
StackundNativeTabs. Benutzerdefinierte JS-Header und<TabList>sind nicht rail-bewusst (SDK 58 bietet nur minimale Kompatibilität) und<TabList>kann nicht in die Rail gesetzt werden. SafeAreaView(react-native-safe-area-context) auf jedem Bildschirm, horizontale Insets inklusive.- Jede Pose testen. Falten, entfalten, drehen, Split View.
- Nur wo nötig anpassen:
ArrangementView,preferredPlacement, reservierte Bereiche, Hinge-APIs oder Deaktivieren der Rail.
Safe Areas (hier treten die meisten Fehler auf)
- Die Rail benötigt einen 84pt Inset auf der linken oder rechten Seite.
- Wickle Scroll-Inhalte in
SafeAreaView edges={['left','right']}, nicht in denScrollView, damit Hintergründe randlos laufen. SetzecontentInsetAdjustmentBehavior="automatic"auf dem ScrollView (RN-Patch #59194 sollte dies allein ausreichend machen). - Horizontale Karussells: Fügen selbst End-Padding hinzu.
- Schwebende Buttons und Toasts:
right: insets.right + 16. Bei RTL beideleftundrightlesen. - Innerhalb von
NativeTabsbeinhaltetinsets.bottomdie Tab-Leiste nur, wenn sie unten verläuft. Niemals eine feste Tab-Leisten-Höhe hinzufügen. - Den Content-Container mit Insets polstern, nicht die Root-Ansicht.
- Lies
useWindowDimensionszur Renderzeit. Das linke Split-View-Fenster ist 469pt breit.
Expo Router
- Header-Buttons in der Rail stapeln sich in einer Spalte, Zurück oder Schließen zuerst. Nur-Text-Elemente bleiben horizontal. Benutzerdefinierte Ansichten sind ausgeschlossen.
- Eine Live Activity im Dynamic Island kann Symbolleisten in den Überlaufbereich schieben. Nicht dagegen ankämpfen.
<SplitView>umschließt die systemeigene Split-View: eine Spalte gefaltet, zwei offen, faltbewusst. Experimentell in SDK 58.NativeTabspreferredPlacement:'sidebar' | 'tabBar' | 'automatic'. Gilt auf dem offenen Display im Querformat.
Zusätzliche APIs
| Bedarf | API |
|---|---|
| Zweispaltiger Inhalt (nur iOS 27.1, ältere Versionen rendern Kinder ohne Container) | ArrangementView von @expo/ui/swift-ui, innerhalb eines Host, mit ArrangementView.Primary und .Secondary. Styling mit arrangementViewStyle('split' | 'overlay'), splitArrangementLayoutSize, overlayArrangementEdge. Modifiers von @expo/ui/swift-ui/modifiers. |
| Falt- und Verdeckungsrahmen | npx expo install react-native-reserved-regions, dann gibt useReservedRegions() { kind: 'division' | 'occlusion', frame } in Punkten zurück. Gut für benutzerdefinierte Spielsteuerungen und gleichmäßige Spaltenraster. Nicht verwenden, um Inhalte an der Falte zu teilen – dafür ArrangementView verwenden. |
| Scharnierwinkel und -status | npx expo install expo-hinge. useHinge(), getHinge(), addHingeListener(). Status: closed, partiallyOpen, fullyOpen, unknown. Der SwiftUI-Modifier onHingeChange kann über useWorkletCallback auf dem UI-Thread ausgeführt werden. Funktioniert mit Reanimated und Android 11+ Foldables. |
| Rail-Kante | useVerticalBarEdgeIOS() gibt 'leading', 'trailing' oder null zurück. Kein Duo-Detektor (offenes Hochformat gibt null zurück). |
| Rail deaktivieren | <StatusBar verticalBarBehaviorIOS="disabled" /> (mapped auf preferredVerticalBarBehavior). Nur für Videoplayer oder Taschenrechner-artige Bildschirme, und die Wahl pro Bildschirm stabil halten. |
Orientierung
Unter iOS 27 sind Orientierungssperren Präferenzen in veränderbaren Kontexten. lockAsync kann verweigert werden und requireFullScreen wählt nicht mehr ab. Das Sperren auf Hoch- oder Querformat auf Duo funktioniert nur, wenn die App den Bildschirm durch Posenänderungen hindurch füllt.
Bekannte Grenzen
- StandBy ist im Duo-Simulator nicht verfügbar, und die meisten App-Erweiterungen sind im Simulator von Xcode 27.1 nicht verfügbar. Teste Widgets und Erweiterungen auf einem Gerät.
expo-app-intents(Alpha, ebenfalls SDK 58) legt Aktionen für Siri, Shortcuts und Spotlight frei. Einige Schemas benötigen ein neueres iOS als ihre Domäne (.mail.openDraftbenötigt iOS 27). Unabhängig vom Layout.
Test-Checkliste
- Gefaltet: Nichts Wichtiges unter der Rail, Buttons reagieren.
- Offen: Layout schaltet keine Designs um, nichts unter der Rail, Hochformat-Leisten kehren nach oben und unten zurück.
- Teilweise offen: Nichts Wesentliches in der Falte, Blätter und Popover landen auf einer Seite.
- Split View: Inhalt frei von der Rail (linke Kante des linken Fensters).
- Falten bei mittigem Bildschirm: Scroll-Position, Zustand und Aktionen bleiben erhalten.
- Standard-iPhone: Nichts ändert sich.
Audit-Workflow
- SDK 58 bestätigen und neu bauen.
- Nach benutzerdefinierten Headern oder Tab-Leisten suchen (grep
headerShown: falseplus handgebaute Leisten,TabList) und zuStackundNativeTabsmigrieren. - Nach hartcodierten
top,bottom,left,rightOffsets, festen Tab-Leisten-Höhen und Root-Level-Padding suchen (grep). Durch Insets ersetzen. - Karussells, FABs und Toasts gemäß der Safe-Area-Liste prüfen.
- Zweispaltige Bildschirme auswählen:
SplitViewoderArrangementView. - Pro Bildschirm entscheiden, ob reservierte Bereiche oder der Hinge nur dann benötigt werden, wenn ein benutzerdefiniertes Layout sie wirklich braucht.
- Im Device Hub verifizieren, dann in CI (siehe unten).
CI: Auf dem Duo-Simulator ausführen
Den Skill expo:eas-workflows verwenden (zuerst das Live-Schema abrufen, keine Job-Parameter raten). Eine sinnvolle Form aus dem CI/CD-Tutorial: PR-getriggerter Simulator-Build auf dem Image sdk-58 oder latest, optional Maestro E2E, dann fingerprint-basierte Wahl zwischen nativen Build und EAS Update für die Produktion. Mit npx -y eas-cli@latest workflow:validate .eas/workflows/<file>.yml --non-interactive validieren. Workflows verbrauchen bezahlte EAS-Minuten.
Häufige Fehler
| Fehler | Korrektur |
|---|---|
| ScrollView mit Insets polstern | SafeAreaView darum, contentInsetAdjustmentBehavior="automatic" |
| Feste Tab-Leisten-Höhe im Padding | insets.bottom aus der Safe Area verwenden |
useVerticalBarEdgeIOS() zur Duo-Erkennung verwenden | Es ist nur die Rail-Kante, null im offenen Hochformat |
| Inhalte an der Falte mit reservierten Bereichen teilen | ArrangementView oder SplitView |
| Handgebaute JS-Header oder Tab-Leiste | Stack und NativeTabs |
Schwebender Button bei right: 16 | right: insets.right + 16 |
Annahme, dass lockAsync immer erfolgreich ist | Als Präferenz behandeln und den Bildschirm füllen |
| Nur auf einem Standard-iPhone testen | Device Hub, jede Pose |