En los seis Tech Talks sobre el iPhone Duo no hay ni una línea de Objective-C. Era de esperar — Apple enseña lo nuevo en Swift y SwiftUI. Pero la realidad de la industria es otra: apps bancarias, mensajeros, todo lo que se escribió desde los 2010 y sobrevivió a cinco rediseños lleva dentro un núcleo ObjC. En ocho años en iOS he visto suficientes bases de código así como para saberlo: «lo reescribimos en Swift y luego lo adaptamos» es un plan que no va a ocurrir jamás. Habrá que adaptar lo que hay.
La buena noticia: casi todo lo que el Duo necesita vive en UIKit, y UIKit sigue hablando Objective-C. En este artículo: qué está disponible directamente, qué a través de análogos UIKit, y en qué dos sitios no hay manera de evitar una capa de Swift. Las firmas salen de los Tech Talks de septiembre de 2026; hasta que salga el Xcode 27.1 estable, contrasta la escritura con las cabeceras del SDK.
Paso 0: recompilar y auditar#
La primera acción no depende del lenguaje: compilar el proyecto con Xcode 27.1 contra el SDK de iOS 27.1. Sin eso no habrá ni edge-to-edge, ni barras verticales, ni APIs nuevas — la app simplemente recibirá márgenes en los bordes de la pantalla.
Después, grep. Cada hallazgo de estos patrones es un defecto potencial en el Duo:
[UIScreen mainScreen] → ambiguous with two screens
interfaceOrientation → the inner display ignores orientations
UI_USER_INTERFACE_IDIOM() → Duo is not a new idiom; idiom branches break
userInterfaceIdiom
safeAreaInsets.left * 2 → insets are asymmetric
hardcoded widths (390, 428, ...) → width changes with a flick of the wristEn bases de código viejas, [[UIScreen mainScreen] bounds] aparece por decenas — en cálculos de frames, en configuración de collection views, en cachés de tamaños. Sustitutos:
// Scale comes from the trait collection, not the screen
CGFloat scale = self.traitCollection.displayScale;
// If you actually need the screen (rare) — via the window scene
UIScreen *screen = self.view.window.windowScene.screen;
// Layout size — your own view's bounds inset by the safe area
CGRect content = UIEdgeInsetsInsetRect(self.view.bounds,
self.view.safeAreaInsets);Size classes en vez de orientaciones — a la vieja usanza, pero en serio#
El API de trait collections no ha cambiado desde iOS 8 — lo que ha cambiado es el precio de ignorarlo. Una app bloqueada en vertical rotará en la pantalla interior del Duo independientemente de supportedInterfaceOrientations, así que toda la lógica de «en landscape lo mostramos distinto» tiene que mudarse a size classes:
- (void)traitCollectionDidChange:(UITraitCollection *)previous {
[super traitCollectionDidChange:previous];
if (previous.horizontalSizeClass == self.traitCollection.horizontalSizeClass) {
return;
}
BOOL isWide = self.traitCollection.horizontalSizeClass ==
UIUserInterfaceSizeClassRegular;
// compact — the Duo's outer screen (and every regular iPhone),
// regular — the inner screen (and iPad)
[self rebuildLayoutForWideMode:isWide];
}En iOS 17+ en lugar de sobreescribir traitCollectionDidChange: puedes usar la observación registrada (registerForTraitChanges:) — también está expuesta en Objective-C.
Un punto aparte de la auditoría: Auto Layout frente a frames manuales. Las constraints contra safeAreaLayoutGuide sobrevivirán al Duo sin retoques; el cálculo manual de frames con self.view.frame.size.width y desplazamientos, no. La prioridad de migración es para lo segundo.
Barras: limpiar lo casero, marcar lo del sistema#
La regla del Tech Talk 111462 es la que más duele al código legacy: en la disposición vertical participan solo los contenedores del sistema — UINavigationController y UITabBarController. Un UIToolbar personalizado añadido como subview, una «cabecera» casera hecha de UIView con botones — todo eso seguirá horizontal y entrará en conflicto con la barra vertical del sistema.
Si las barras son del sistema, el marcado del nuevo comportamiento se hace íntegramente desde Objective-C:
// Pin the primary action in the vertical bar
self.navigationItem.pinnedTrailingGroup =
[UIBarButtonItemGroup fixedGroupWithRepresentativeItem:nil
items:@[sendButton]];
// Custom back/close as the leading item; opt out of the system back button
self.navigationItem.leftItemsSupplementBackButton = NO;
// Secondary actions go to the overflow menu
self.navigationItem.additionalOverflowItems =
[UIDeferredMenuElement elementWithProvider:^(void (^completion)(NSArray *)) {
completion(@[self.shareAction, self.exportAction]);
}];
// Priorities: what moves to overflow first when space runs out
filterButton.visibilityPriority = UIBarButtonItemVisibilityPriorityLow;
// A badge instead of a text counter
inboxButton.badge = [UIBarButtonItemBadge countBadgeWithInteger:7];La barra vertical es estrecha y sus elementos son solo iconos. Un botón de texto «Enviar todo» o un segmented control no caben ahí — esos elementos se quedan en la navigation bar. Y el opt-out para un controller concreto es sobreescribir preferredVerticalBarBehavior.
Escenas: una deuda que toca devolver#
Muchas apps ObjC siguen viviendo en el ciclo de vida centrado en el AppDelegate, con la migración a escenas pospuesta «para tiempos mejores». El Duo ha puesto fecha a esos tiempos: es el primer iPhone con varias ventanas de una misma app y Split View para todas. La migración en sí es la estándar (UIApplicationSceneManifest con UIApplicationSupportsMultipleScenes en el Info.plist, UIWindowSceneDelegate en lugar del enredo con el AppDelegate) — no hay claves específicas del Duo.
El comportamiento nuevo es uno, pero traicionero: las ventanas solo se crean en la pantalla interior. Con el dispositivo cerrado, la petición de escena falla:
UISceneSessionActivationRequest *request =
[UISceneSessionActivationRequest requestWithRole:UIWindowSceneSessionRoleApplication];
[UIApplication.sharedApplication activateSceneSessionForRequest:request
errorHandler:^(NSError *error) {
// Device closed: no new windows — open in the current window instead
[self openDocumentInCurrentWindow];
}];Los botones «abrir en ventana nueva» de tu UI deben, o bien usar el affordance de activación del sistema de UIWindowScene (se oculta solo cuando las ventanas no están disponibles), o bien gestionar el error honestamente.
Reserved regions y bisagra desde Objective-C#
La geometría del pliegue y de las cámaras está disponible a nivel de UIView:
NSArray<UIViewReservedRegion *> *folds =
[self.view reservedRegionsWithKind:UIViewReservedRegionKindDivision
options:0];
if (folds.count > 0) {
CGRect foldFrame = folds.firstObject.frame;
// E.g. don't place a floating button inside this rectangle
}UIHingeInteraction es de la familia UIInteraction, o sea, también compatible con ObjC: [view addInteraction:]. Pero antes de estirar la mano hacia el ángulo de la bisagra conviene preguntarse si de verdad hace falta: Apple prohíbe expresamente el layout basado en el ángulo, y los componentes del sistema (sheets, alerts, menús, barras del sistema) esquivan el pliegue sin tu participación.
Dónde sí hace falta Swift#
La frontera honesta es esta. Los exclusivos de SwiftUI — onHingeChange, ArrangementView, .sceneAccessory con CameraCaptureAccessory — no están disponibles desde Objective-C, pero los dos primeros tienen análogos UIKit completos (UIHingeInteraction, UIArrangementViewController), y a la mayoría de las apps legacy les bastan.
La capa de Swift hará falta en dos casos. El primero: apps de cámara con gestión explícita de módulos — AVCaptureDeviceDirectionCoordinator está aislado en el main actor y pasa el dispositivo mediante AVCaptureDeviceDescriptor; trabajar con eso desde ObjC es formalmente posible, pero el modelo de actores de Swift Concurrency no existe en ObjC, y es más seguro encapsular la coordinación de cámaras en un tipo Swift aparte con fachada compatible con ObjC. El segundo: features de doble pantalla tipo teleprompter en el display exterior — CameraCaptureAccessory está declarado vía el scene accessory API de SwiftUI.
El patrón es siempre el mismo: una clase @objc pequeña en Swift que encapsula el API nuevo y expone al código legacy un delegado o un bloque. Así se sobrevivió ya a HealthKit, WidgetKit y App Intents — en ese sentido el Duo no ha inventado nada.
Plan para el trimestre#
Reducida a una lista, la adaptación de una app ObjC queda así:
- Recompilar con el SDK de iOS 27.1 y pasarla por el simulador del Duo (Device Hub).
- Auditoría con grep:
mainScreen, orientaciones, idioms, insets simétricos, anchos hardcodeados. - Migrar las barras caseras a contenedores del sistema + marcar pinned/overflow.
- Escenas: manifest,
UIWindowSceneDelegate, gestión del fallo al crear ventana. - Capas Swift puntuales para cámara y doble pantalla, si el producto las necesita.
Los puntos 1–2 son días; el 3–4, semanas, y dependen de la deuda acumulada; el 5 no lo necesita todo el mundo. Hasta el 23 de octubre, cuando el dispositivo llegue a manos de los usuarios, tiempo para los dos primeros puntos tiene cualquier equipo.



