El 9 de septiembre Apple presentó el iPhone Duo, el primer iPhone plegable. Mientras los usuarios discuten el precio ($1999) y la cámara bajo la pantalla, a nosotros Apple nos ha dejado seis Tech Talks, una página de HIG y la promesa de una beta de Xcode 27.1 antes de que acabe el mes. Me he visto las seis sesiones y aquí reúno lo que casi toda app va a tener que hacer — con código en Swift y en Objective-C, porque la parte UIKit de las nuevas APIs está disponible desde ambos lenguajes y en el mundo hay bastantes más bases de código legacy de las que nos gusta admitir.
Una aclaración honesta antes de empezar: el dispositivo sale el 23 de octubre y, en el momento de escribir esto, Xcode 27.1 sigue en estado de «coming later this month». Todas las firmas de abajo vienen de los Tech Talks oficiales, pero hasta contrastarlas con las cabeceras del SDK son «la forma del API», no una garantía letra por letra.
No es un idiom nuevo, es un continuo de tamaños#
Lo primero que Apple repite en casi cada charla: el iPhone Duo no es «una nueva clase de dispositivo» que exija una interfaz aparte. Sigue siendo un iPhone; simplemente el rango de tamaños disponibles se ha ensanchado.
En concreto. La pantalla exterior (5.4″) se comporta como un iPhone normal: ancho compact, orientaciones de siempre. La interior (7.6″) es casi un iPad: regular × regular en cualquier orientación. Y aquí viene el detalle que romperá más apps: la pantalla interior ignora supportedInterfaceOrientations. Una app bloqueada en vertical rotará igualmente en la pantalla interior. No hay forma de impedirlo.
| Configuración | Horizontal | Vertical |
|---|---|---|
| Pantalla exterior, vertical | compact | regular |
| Pantalla exterior, horizontal | compact | compact |
| Pantalla interior, cualquier orientación | regular | regular |
De ahí la regla principal: cualquier ramificación por orientación o por idiom del dispositivo (userInterfaceIdiom, «si es iPad, dos columnas») se convierte en un defecto. Solo se puede ramificar por size classes:
// SwiftUI
@Environment(\.horizontalSizeClass) private var hSizeClass
var body: some View {
if hSizeClass == .regular {
TwoColumnLayout()
} else {
SingleColumnLayout()
}
}// Objective-C: same question, via the trait collection
- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection {
[super traitCollectionDidChange:previousTraitCollection];
BOOL isWide = self.traitCollection.horizontalSizeClass ==
UIUserInterfaceSizeClassRegular;
[self applyLayoutForWideMode:isWide];
}Si en tu base de código hay una rama cuyo sentido es «si esto es un iPhone Duo», es la señal para pararse y reformularla como «cuánto espacio hay disponible ahora mismo».
Tres niveles de SDK: qué te da recompilar#
El comportamiento de la app en el Duo depende del SDK con el que se compiló. Hay tres niveles:
- SDK anterior a iOS 27. Funciona sin cambios, pero «en un buzón»: con el dispositivo cerrado el contenido no llega a la barra de estado ni a la cámara; abierto, el tamaño de siempre con márgenes.
- SDK de iOS 27. El contenido se extiende a la zona de la barra de estado en la pantalla interior.
- SDK de iOS 27.1 (Xcode 27.1). Edge-to-edge completo, las barras del sistema se reorganizan en vertical automáticamente y se desbloquean las APIs de reserved regions y arrangements.
Recompilar con 27.1 es el cambio más barato y más rentable de todos los posibles. Sin él, el resto de la adaptación simplemente no está disponible.
Caza de suposiciones cableadas#
Cada bug de un port a plegable es alguna suposición fija en el código. Aquí van tres que merece la pena grepear ya.
UIScreen.main está muerto. En un dispositivo con dos pantallas, «la pantalla principal» es un concepto ambiguo, y Apple dice abiertamente que el API va camino de la deprecación. Sustitutos:
// Before
let scale = UIScreen.main.scale
// After
let scale = traitCollection.displayScale
// If you actually need the screen
let screen = view.window?.windowScene?.screen// Objective-C
CGFloat scale = self.traitCollection.displayScale;
UIScreen *screen = self.view.window.windowScene.screen;Matemática simétrica de insets. En el Duo la safe area es asimétrica con frecuencia: la barra vertical y la Dynamic Island se sientan en un lado, y en cuál exactamente depende de la postura del dispositivo y de en qué mitad del Split View vive tu app. El clásico width - insets.left * 2 ahora calcula mal:
// ❌ assumes symmetry
let width = view.bounds.width - view.safeAreaInsets.left * 2
// ✅ each edge on its own
let width = view.bounds.inset(by: view.safeAreaInsets).width// Objective-C
CGRect contentFrame = UIEdgeInsetsInsetRect(self.view.bounds,
self.view.safeAreaInsets);Anchos hardcodeados. Tablas de «ancho del dispositivo → layout», breakpoints tipo width > 390... todo eso se desmonta en un dispositivo que cambia de ancho con un gesto de la mano del usuario.
Barras verticales: gratis, pero solo con contenedores del sistema#
Las dos pantallas del Duo son más anchas y más bajas que un iPhone normal, así que los controles del sistema se mudan al borde lateral: barra de estado, Dynamic Island (ahora vertical), toolbars y tab bars. La app lo recibe automáticamente — con dos condiciones: compilar contra el SDK de iOS 27.1 y usar contenedores de barras del sistema.
Los UIToolbar, UINavigationBar y UITabBar personalizados, añadidos a mano a la jerarquía de vistas, no participan en absoluto en la disposición vertical. Participan solo UINavigationController, UITabBarController y el .toolbar de SwiftUI. Si tienes un panel de botones casero, es el primer candidato a migrar.
Del API nuevo, lo más útil:
// SwiftUI: pin the primary action, send secondary ones to overflow
.toolbar {
ToolbarItem(placement: .topBarPinnedTrailing) {
Button("Post", action: post)
}
ToolbarItem(placement: .cancellationAction) {
Button("Close", action: close)
}
}// Objective-C: same semantics via UINavigationItem
self.navigationItem.pinnedTrailingGroup =
[UIBarButtonItemGroup fixedGroupWithRepresentativeItem:nil
items:@[postButton]];
// Secondary actions go to the vertical bar's overflow menu
self.navigationItem.additionalOverflowItems =
[UIDeferredMenuElement elementWithProvider:^(void (^completion)(NSArray *)) {
completion(@[shareAction, archiveAction]);
}];La barra vertical tiene un ancho fijo, así que Apple recomienda directamente botones de solo símbolo: las etiquetas de texto y los segmented controls no caben — esos elementos déjalos en la navigation bar. Renunciar a la disposición vertical en una pantalla concreta se puede (.toolbarVerticalBehavior(.disabled) en SwiftUI, preferredVerticalBarBehavior en UIKit), pero es una decisión consciente, no el default.
Split View y varias ventanas — ahora en el iPhone#
El Duo es el primer iPhone con Split View: dos apps lado a lado al 50/50, y participan todas las apps sin opt-in alguno. Tu app puede acabar en media pantalla en cualquier momento — una razón más por la que los anchos fijos ya no funcionan.
La segunda novedad: varias escenas de una misma app, también por primera vez en el iPhone. Si ya soportas multiventana en iPad (UIApplicationSupportsMultipleScenes en el Info.plist, escenas en lugar del ciclo de vida centrado en el AppDelegate), la mayor parte del trabajo está hecha. No hay claves de Info.plist específicas del Duo.
Pero hay un comportamiento que en iPad no existía: las ventanas nuevas solo se crean en la pantalla interior. Con el dispositivo cerrado, la petición de una nueva escena fallará, y hay que gestionarlo:
// Objective-C: a scene request can now fail
UISceneSessionActivationRequest *request =
[UISceneSessionActivationRequest requestWithRole:UIWindowSceneSessionRoleApplication];
[UIApplication.sharedApplication activateSceneSessionForRequest:request
errorHandler:^(NSError *error) {
// Device is closed — new windows are unavailable
[self showSingleWindowFallback];
}];En SwiftUI, Apple recomienda el affordance estándar UIWindowSceneActivation: se oculta solo cuando crear una ventana no está disponible y te ahorra la gestión manual de la mitad de los casos.
Mi plan, en forma de checklist#
Para que esto no se quede en teoría, esto es lo que voy a hacer con mis apps (MeteoHealth la primera) cuando llegue Xcode 27.1:
- Recompilar con el SDK de iOS 27.1 y abrir el simulador del Duo: el Device Hub de Xcode 27.1 sabe abrir, cerrar y plegar el dispositivo virtual. Los bugs que dependen de la postura no se encuentran leyendo código.
- Grepear suposiciones:
UIScreen.main, ramas por orientación e idiom, insets simétricos, anchos hardcodeados. - Revisar las barras: yo uso SwiftUI y contenedores del sistema, así que la disposición vertical debería llegarme gratis — pero el orden de los botones y las prioridades de overflow hay que mirarlos con los ojos igualmente.
- Pasar por los dos escenarios de Split View (la app a la izquierda y a la derecha) y las dos mitades.
La conclusión honesta de las seis charlas: si tu app ya se redimensiona con libertad — usa size classes, respeta la safe area borde a borde, vive en contenedores del sistema — el iPhone Duo apenas le cambia nada. Todo el dolor se lo llevan las apps con suposiciones cableadas. Su tiempo se acaba el 23 de octubre.
En el siguiente artículo de la serie desmonto el API de la bisagra — onHingeChange y UIHingeInteraction — y por qué Apple prohíbe construir el layout a partir del ángulo de plegado.



