Expo en iPhone Duo
Fuente: https://expo.dev/blog/supporting-iphone-duo (Expo SDK 58). Las reglas de diseño viven en apple-design-duo; este skill es la capa de implementación de Expo y React Native. Contexto del pipeline de CI: https://docs.expo.dev/tutorial/cicd/introduction/ y el skill expo:eas-workflows.
Requisitos
- Expo SDK 58 (orientado a iOS 27) y Xcode 27.1 (runtime iOS 27.1 más el simulador de iPhone Duo).
- Imagen de compilación EAS:
sdk-58olatest. - Xcode 27 reemplaza Simulator.app con Device Hub (Xcode > Open Developer Tool > Device Hub). Tiene un Duo 3D que se pliega, rota y ajusta el ángulo de la bisagra.
Modelo mental
Plegado = ancho compacto, abierto = ancho regular. Deja que los diseños se ensanchen, nunca cambies de diseño. Mismas funcionalidades y estado en ambos lados. En poses tipo libro, iOS mueve la barra de estado, la barra de pestañas y la barra de herramientas a un carril vertical en un borde.
Las cuatro reglas
- Deja que iOS dibuje la navegación. Usa
Stackde Expo Router yNativeTabs. Los encabezados JS personalizados y<TabList>no son conscientes del carril (SDK 58 solo agrega compatibilidad mínima) y<TabList>no puede ir en el carril. SafeAreaView(react-native-safe-area-context) en cada pantalla, incluyendo las inserciones horizontales.- Prueba cada pose. Pliega, despliega, rota, Split View.
- Ajusta solo donde sea necesario:
ArrangementView,preferredPlacement, regiones reservadas, APIs de bisagra, o desactivando el carril.
Áreas seguras (donde ocurren la mayoría de errores)
- El carril necesita una inserción de 84pt a la izquierda o derecha.
- Envuelve el contenido desplazable en
SafeAreaView edges={['left','right']}, no elScrollView, para que los fondos se extiendan de borde a borde. EstablececontentInsetAdjustmentBehavior="automatic"en el ScrollView (el parche RN #59194 debería hacer que esto sea suficiente por sí solo). - Carruseles horizontales: añaden su propio padding al final.
- Botones flotantes y toasts:
right: insets.right + 16. En RTL, lee tantoleftcomoright. - Dentro de
NativeTabs,insets.bottomincluye la barra de pestañas solo cuando está en la parte inferior. Nunca agregues una altura fija de barra de pestañas. - Aplica padding al contenedor de contenido con las inserciones, no a la vista raíz.
- Lee
useWindowDimensionsen tiempo de renderizado. La ventana izquierda de Split View tiene 469pt de ancho.
Expo Router
- Los botones de encabezado en el carril se apilan en una columna, primero Atrás o Cerrar. Los elementos solo de texto permanecen horizontales. Las vistas personalizadas están excluidas.
- Una Live Activity en la Dynamic Island puede reducir las barras de herramientas al desbordamiento. No luches contra ello.
<SplitView>envuelve la vista dividida del sistema: una columna plegada, dos abierta, consciente del pliegue. Experimental en SDK 58.preferredPlacementdeNativeTabs:'sidebar' | 'tabBar' | 'automatic'. Se aplica en la pantalla abierta en horizontal.
APIs adicionales
| Necesidad | API |
|---|---|
| Contenido de dos paneles (solo iOS 27.1, versiones anteriores renderizan hijos sin contenedor) | ArrangementView de @expo/ui/swift-ui, dentro de un Host, con ArrangementView.Primary y .Secondary. Estiliza con arrangementViewStyle('split' | 'overlay'), splitArrangementLayoutSize, overlayArrangementEdge. Modificadores de @expo/ui/swift-ui/modifiers. |
| Marcos de pliegue y oclusión | npx expo install react-native-reserved-regions, luego useReservedRegions() devuelve { kind: 'division' | 'occlusion', frame } en puntos. Bueno para controles de juego personalizados y cuadrículas de columnas pares. No lo uses para dividir contenido en el pliegue, usa ArrangementView. |
| Ángulo y estado de la bisagra | npx expo install expo-hinge. useHinge(), getHinge(), addHingeListener(). Estado: closed, partiallyOpen, fullyOpen, unknown. El modificador SwiftUI onHingeChange puede ejecutarse en el hilo de la UI mediante useWorkletCallback. Funciona con Reanimated y plegables con Android 11+. |
| Borde del carril | useVerticalBarEdgeIOS() devuelve 'leading', 'trailing' o null. No es un detector de Duo (retrato abierto devuelve null). |
| Desactivar el carril | <StatusBar verticalBarBehaviorIOS="disabled" /> (mapea a preferredVerticalBarBehavior). Solo para reproductores de video o pantallas tipo calculadora, y mantén la elección estable por pantalla. |
Orientación
En iOS 27, los bloqueos de orientación son preferencias en contextos redimensionables. lockAsync puede ser rechazado y requireFullScreen ya no opta por salir. Bloquear a retrato o paisaje en Duo funciona solo si la app llena la pantalla a través de los cambios de pose.
Limitaciones conocidas
- StandBy no está disponible en el simulador de Duo, y la mayoría de las extensiones de app no están disponibles en el simulador de Xcode 27.1. Prueba widgets y extensiones en dispositivo.
expo-app-intents(alfa, también SDK 58) expone acciones a Siri, Shortcuts y Spotlight. Algunos esquemas necesitan una versión de iOS más reciente que su dominio (.mail.openDraftnecesita iOS 27). No relacionado con el diseño.
Lista de verificación de pruebas
- Plegado: nada importante debajo del carril, los botones responden.
- Abierto: el diseño no cambia de diseño, nada debajo del carril, las barras de retrato vuelven a la parte superior e inferior.
- Parcialmente abierto: nada esencial en el pliegue, las hojas y los popovers caen en un lado.
- Split View: contenido limpio del carril (borde izquierdo de la ventana izquierda).
- Plegando en medio de la pantalla: la posición de desplazamiento, el estado y las acciones persisten.
- iPhone estándar: nada cambia.
Flujo de trabajo de auditoría
- Confirma SDK 58 y recompila.
- Busca encabezados o barras de pestañas personalizados (
headerShown: falsemás barras hechas a mano,TabList) y muévete aStackyNativeTabs. - Busca desplazamientos fijos de
top,bottom,left,right, alturas fijas de barra de pestañas y padding a nivel raíz. Reemplázalos con inserciones. - Revisa carruseles, FABs y toasts según la lista de áreas seguras.
- Elige pantallas de dos paneles:
SplitViewoArrangementView. - Decide por pantalla si usar regiones reservadas o bisagra solo si un diseño personalizado realmente las necesita.
- Verifica en Device Hub, luego en CI (abajo).
CI: ejecútalo en el simulador de Duo
Usa el skill expo:eas-workflows (obtén el esquema en vivo primero, no adivines los parámetros del trabajo). Una forma sensata del tutorial de CI/CD: compilación de simulador desencadenada por PR en la imagen sdk-58 o latest, E2E opcional con Maestro, luego una elección basada en huella entre una compilación nativa y una EAS Update para producción. Valida con npx -y eas-cli@latest workflow:validate .eas/workflows/<file>.yml --non-interactive. Los Workflows consumen minutos EAS pagados.
Errores comunes
| Error | Corrección |
|---|---|
| Aplicar padding al ScrollView con inserciones | SafeAreaView alrededor, contentInsetAdjustmentBehavior="automatic" |
| Altura fija de barra de pestañas en padding | Usa insets.bottom del área segura |
Usar useVerticalBarEdgeIOS() para detectar Duo | Es solo el borde del carril, null en retrato abierto |
| Dividir contenido en el pliegue con regiones reservadas | ArrangementView o SplitView |
| Encabezado o barra de pestañas JS hecha a mano | Stack y NativeTabs |
Botón flotante en right: 16 | right: insets.right + 16 |
Asumir que lockAsync siempre tiene éxito | Trátalo como una preferencia y llena la pantalla |
| Probar solo en un iPhone estándar | Device Hub, cada pose |