← Todos los articulos

Cinco plataformas de Apple, tres archivos compartidos: cómo Return realmente se publica como SwiftUI multiplataforma

Return, mi temporizador de meditación, funciona en cinco plataformas de Apple: iPhone, iPad, Mac, Apple Watch y Apple TV.1 El código base tiene 40 archivos Swift (sin contar los tests). Tres de ellos se comparten entre las cinco plataformas. El resto se reparte en targets de Xcode separados que duplican conceptos como TimerManager, AudioManager y ContentView en lugar de compartirlos mediante compilación condicional con #if os(...).

La tasa de código compartido es de aproximadamente 7,5%, y es intencional.

Este ensayo trata sobre cómo se ve realmente la publicación de una app SwiftUI multiplataforma en 2026, por qué compartir código de forma agresiva está sobrevalorado, y qué tienen en común los tres archivos que se comparten.

iOS 26 platform tile from Apple Developer iPadOS 26 platform tile from Apple Developer macOS 26 platform tile from Apple Developer watchOS 26 platform tile from Apple Developer tvOS 26 platform tile from Apple Developer

Las cinco plataformas a las que apunta Return, tal como Apple las presenta en developer.apple.com. Cada una es un target de plataforma distinto en Xcode, no una rama en tiempo de ejecución.

TL;DR

  • Return: 18 archivos Swift en el target principal (iOS + iPadOS + macOS), 10 archivos en el target de tvOS, 7 en el target de watchOS, 2 archivos de widgets (Live Activities) y 3 archivos verdaderamente multiplataforma en Return/Shared/. Total: 40.
  • Los tres archivos compartidos son los que están cerca de la persistencia: MeditationSession, SessionStore, SessionHistoryView. Estado que viaja por iCloud, no UI que se adapta a la plataforma.
  • tvOS y watchOS son targets de Xcode separados, no ramas #if os(tvOS) dentro del target principal. Los modelos de control son demasiado distintos para que quepan en un solo ContentView.
  • Incluso dentro del target principal de iOS/iPadOS/macOS, los bloques #if os proliferan: 10 en ContentView.swift, 8 en LiveActivityManager.swift, 8 en VideoBackgroundView.swift, 6 en AudioManager.swift.
  • La conclusión honesta: compartir código de forma agresiva entre cinco plataformas de Apple es un lastre de mantenimiento. Un núcleo compartido pequeño (la capa de persistencia) más interfaces específicas separadas para cada plataforma se publica más rápido y se rompe menos que un solo archivo gigante repleto de #if.

Para los complementos específicos de cada plataforma, lee la matriz de plataformas Apple, el contrato de tiempo de ejecución de watchOS y los patrones SwiftUI de Liquid Glass.

Los números

La forma del código base, contando archivos Swift, después de eliminar los tests y los UI tests:

Return/                            18 files   (iPhone + iPad + Mac, single target)
├── Shared/                         3 files     cross-platform truth   ├── MeditationSession.swift   ├── SessionStore.swift   └── SessionHistoryView.swift
├── ContentView.swift              (10 #if os branches)
├── TimerManager.swift             (2 #if os branches)
├── AudioManager.swift             (6 #if os branches)
├── HealthKitManager.swift
├── LiveActivityManager.swift      (8 #if os branches, iOS-only)
├── ThemeManager.swift
├── VideoBackgroundView.swift      (8 #if os branches)
├── GlassTextShape.swift           (Liquid Glass, see prior post)
├── GlassTimerText.swift
└──  (settings, theme, audio assets, etc.)

ReturnTV/                          10 files   (tvOS, separate target)
├── TVContentView.swift
├── TVTimerManager.swift            duplicates main TimerManager
├── TVAudioManager.swift            duplicates main AudioManager
├── TVDurationPicker.swift
├── TVFocusModifier.swift           tvOS button styles for focus
├── TVSettingsView.swift
└── ReturnWatch Watch App/              7 files   (watchOS, separate target)
├── WatchContentView.swift
├── WatchTimerManager.swift         duplicates main TimerManager
├── WatchAudioManager.swift         duplicates main AudioManager
├── WatchHealthKitManager.swift     duplicates main HealthKitManager (mostly)
├── WatchSettingsView.swift
└── ReturnWidgets/                      2 files   (Live Activity + bundle)
├── ReturnLiveActivity.swift
└── ReturnWidgetsBundle.swift

Cinco plataformas, tres archivos compartidos, dos targets separados por plataforma más un target de widgets, y compilación condicional intensiva dentro del target principal. La proporción de código compartido ronda el 7,5%. La mayoría de los tutoriales de “SwiftUI multiplataforma” sugieren lo contrario: escribir un solo ContentView que se adapta a cada plataforma mediante @Environment(\.horizontalSizeClass) y #if os(...).2 Eso funciona para dos plataformas (iPhone + iPad). Se rompe en cinco.

Qué tienen en común los tres archivos compartidos

Return/Shared/MeditationSession.swift define el value type adyacente a SwiftData:3

struct MeditationSession: Codable, Identifiable, Equatable {
    let id: UUID
    let startDate: Date
    let endDate: Date
    let durationSeconds: Int
    let sourceDevice: DeviceType
    var syncedToHealthKit: Bool

    enum DeviceType: String, Codable, CaseIterable {
        case iPhone, iPad, mac, appleTV, appleWatch
    }
}

El comentario de cabecera del archivo es esencial: // Add this file to: Return, ReturnTV, ReturnWatch Watch App targets. El mismo archivo fuente está referenciado por los tres targets de Xcode, sin enlaces simbólicos ni empaquetado en un Swift package. El sistema de build de Apple compila felizmente un mismo archivo en tres binarios.

SessionStore.swift es la capa de persistencia: un envoltorio fino sobre NSUbiquitousKeyValueStore (el iCloud Key-Value Store de Apple) que lee y escribe arreglos de MeditationSession. La elección importa: la sincronización por KV-store le da a Return un historial de sesiones entre dispositivos sin tener que aprovisionar un contenedor de CloudKit, con la contrapartida de que el almacén entero está limitado a 1 MB en total.12 Para una lista de sesiones de meditación que promedian unos cientos de bytes cada una, ese límite sobra. SessionHistoryView.swift es una lista de SwiftUI que renderiza las sesiones. Ambos archivos los usan de forma idéntica los targets de iPhone, iPad, Mac, Watch y TV.

Lo que estos tres archivos tienen en común: describen estado, no interacción. Una MeditationSession es el mismo concepto en cada dispositivo. La lista de sesiones pasadas se lee igual en cada dispositivo. Ninguno involucra una superficie de control, un gestor de ventanas, una decisión de ruteo de audio, un motor de foco, ni una corona digital. En el momento en que un archivo necesita saber sobre qué plataforma está corriendo, deja de ser compartible.

Por qué el resto no se compartió

Toma TimerManager. La versión para iOS/iPadOS/macOS usa Timer.publish(every: 1, ...) y enruta las notificaciones a través de UserNotifications. La versión para tvOS (TVTimerManager) maneja el caso en que el usuario pausó con el Siri Remote y entra el protector de pantalla. La versión para watchOS (WatchTimerManager) delega en una WKExtendedRuntimeSession (vía WatchSessionManager) para que el sistema operativo mantenga la app receptiva mientras la pantalla se atenúa, y enruta la entrada por la corona digital en lugar del táctil. Tres plataformas, tres comportamientos de temporizador profundamente distintos.

Podrías unificarlos como class TimerManager { #if os(watchOS) ... #elif os(tvOS) ... }. El resultado sería una clase con tres modos, cada uno con cuarenta líneas de código entre #if, donde tocar el camino de iOS arriesga romper el de watchOS. Eso es un horror de mantenimiento.

Tres clases separadas con tres nombres de archivo es más código en disco y menos código en tu cabeza. La duplicación que puedes leer le gana a la abstracción que no.

La misma lógica aplica a:

  • ContentView vs TVContentView vs WatchContentView: los modelos de navegación son distintos (basado en push en iPhone, basado en foco en TV, basado en lista en Watch).
  • AudioManager vs TVAudioManager vs WatchAudioManager: las categorías de sesión de audio difieren, watchOS tiene reglas más estrictas para audio en segundo plano, tvOS enruta de manera distinta a AirPlay.
  • VideoBackgroundView tiene 8 ramas #if os(iOS) en el target principal (con un complemento #elseif os(macOS)), que cubren distintos recursos de video (fire_phone.mp4 vs fire_mac.mp4), distintos tipos de capa y distintas relaciones de aspecto.4

Conviene aclarar: el target principal Return/ agrupa iOS, iPadOS y macOS. Esas tres plataformas comparten más código del que no. El NavigationStack de SwiftUI funciona en las tres. .glassEffect() funciona en las tres. Las diferencias de gestión de ventanas son reales pero manejables dentro de un solo target. tvOS y watchOS fueron donde tracé la línea del target separado.

El caso de tvOS: por qué el motor de foco obligó a un target separado

La navegación en Apple TV está construida en torno al motor de foco.5 Cada elemento de UI con el que el usuario puede interactuar se declara enfocable; las flechas del sistema en el Siri Remote mueven el foco entre elementos; presionar select activa el elemento enfocado. SwiftUI en tvOS expone esto a través de .focusable(), .focusEffect, y tipos ButtonStyle personalizados que responden a @Environment(\.isFocused) para el efecto de inclinación con paralaje que usan las apps de primera parte de Apple. Código de producción real en TVFocusModifier.swift:6

struct TVCapsuleButtonStyle: ButtonStyle {
    var accentColor: Color = .white
    @Environment(\.isFocused) private var isFocused

    func makeBody(configuration: Configuration) -> some View {
        configuration.label
            .colorMultiply(isFocused ? focusedTextColor : accentColor)
            .background(
                Capsule().fill(isFocused
                    ? AnyShapeStyle(accentColor)
                    : AnyShapeStyle(.ultraThinMaterial))
            )
            .clipShape(Capsule())
            .scaleEffect(isFocused ? 1.1 : 1.0)
            .scaleEffect(configuration.isPressed ? 0.95 : 1.0)
            .shadow(color: .black.opacity(isFocused ? 0.3 : 0.1),
                    radius: isFocused ? 20 : 5, y: isFocused ? 10 : 2)
            .animation(.easeInOut(duration: 0.2), value: isFocused)
    }
}

El mismo archivo también define TVCircleButtonStyle para controles cuadrados o circulares. Ambos estilos invierten el color y la translucidez al recibir el foco: los botones sin foco se asientan sobre .ultraThinMaterial, los botones enfocados se rellenan con el color de acento y aumentan escala más sombra. El patrón es estructuralmente específico de tvOS para esta app. @Environment(\.isFocused) está disponible en iOS, iPadOS, macOS, watchOS y tvOS,13 pero la navegación impulsada por el foco es el modelo primario de interacción solo en tvOS, donde el Siri Remote no produce ningún evento de puntero ni de toque. En iPhone o iPad, el control equivalente se prueba por hit-test al tocar; en Mac se posa el cursor o se hace clic. Los estilos de botón en TVFocusModifier.swift asumen que el foco es la principal forma de interacción del usuario y diseñan toda la respuesta visual a su alrededor. No hay una buena forma de escribir un ContentView que maneje el toque en iOS, el hover en Mac y la navegación impulsada por el foco en tvOS en un solo lugar. La estructura de la vista es genuinamente distinta: un ContentView de tvOS es un grafo de filas enfocables, un ContentView de iOS es una pila de toques para actuar.

Lo mismo ocurre con el selector de duración. En iPhone, se desliza desde abajo y acepta toques. En Apple TV, es una fila horizontal de celdas enfocables que el usuario navega con el control remoto. TVDurationPicker.swift es su propio archivo porque el diseño basado en celdas con foco no tiene análogo en el iPhone. Forzarlos a un solo archivo significaría dos UIs no relacionadas pegadas con #if os(tvOS).

El caso de watchOS: sesiones de tiempo de ejecución extendido, HealthKit y una superficie más pequeña

watchOS añade dos restricciones estructurales que las otras plataformas no tienen:

  1. WKExtendedRuntimeSession para mantener la app receptiva mientras la pantalla del reloj se atenúa.8 Sin ella, watchOS suspende agresivamente la app entre cada tic de un segundo y el temporizador se desfasa. Return declara WKBackgroundModes: mindfulness en el Info.plist del target de watchOS para que el sistema operativo reconozca el caso de uso y le otorgue el presupuesto de tiempo de ejecución; la sesión de tiempo de ejecución en sí se crea con el inicializador por defecto WKExtendedRuntimeSession().
  2. Sincronización con iCloud vía NSUbiquitousKeyValueStore, no WatchConnectivity. La sincronización del historial de sesiones de Return va sobre el mismo key-value store que usan los targets de iPhone, iPad y Mac, así que una meditación registrada en el reloj aparece en la vista de historial del iPhone sin ningún mensajeo directo de reloj a teléfono. WatchConnectivity podría ser una opción a futuro para la sincronización de estado en vivo, pero Return eligió el modelo más simple: cada dispositivo escribe en el mismo iCloud KV-store, y la siguiente lectura en cualquier dispositivo ve la unión.

WatchTimerManager.swift es el temporizador del lado del reloj; delega el trabajo de tiempo de ejecución extendido a WatchSessionManager, definido en ReturnWatchApp.swift como final class WatchSessionManager: NSObject, WKExtendedRuntimeSessionDelegate. El TimerManager de iOS no tiene análogo porque las apps de iOS se mantienen receptivas en primer plano sin una sesión de tiempo de ejecución explícita. Meter la lógica del reloj en el TimerManager de iOS vía #if os(watchOS) significaría que el camino de código de iOS importa símbolos de WatchKit que nunca usa, y que el camino de código de watchOS necesita rutas de inicialización que el de iOS no tiene.

WatchHealthKitManager.swift es una variante más pequeña del HealthKitManager principal. Registra los minutos de mindfulness de la misma manera, pero la UX del diálogo de autorización es distinta (el reloj no puede mostrar un HealthKitPermissionSheet). La clase del Watch tiene aproximadamente la mitad del tamaño de la principal.

Qué pasa dentro del target principal de iOS/iPadOS/macOS

Incluso dentro del target principal, compartir no es automático. ContentView.swift tiene diez bloques #if os(macOS) o #if !os(macOS); LiveActivityManager.swift tiene ocho; VideoBackgroundView.swift tiene ocho; AudioManager.swift tiene seis. Las Live Activities son una característica exclusiva del iPhone, así que todo el LiveActivityManager está envuelto en #if os(iOS). El selector de duración en iPhone usa una disposición distinta del selector de duración en iPad y Mac, así que ContentView tiene ramas de disposición paralelas.

El patrón que ha funcionado: #if os(...) para deltas pequeños entre plataformas (comportamiento de teclado distinto, padding distinto, API ausente), target separado para deltas estructurales grandes (foco vs táctil, sesión de entrenamiento vs temporizador). El umbral con el que terminé es “más de unas 10 líneas de ramificación”. Por debajo, la compilación condicional está bien. Por encima, el archivo está haciendo dos trabajos a la vez y el segundo trabajo pertenece a otro target.

Cuándo no publicar en las cinco plataformas

La evaluación honesta.

Sáltate Apple Watch si tu app es densa en información. La pantalla de 46mm no tiene espacio para una lista de 30 elementos, un selector de duración y una página de configuración. Return sobrevive en watchOS porque la interacción central es un solo botón (iniciar/detener un temporizador). Una app de productividad, una app financiera o una app rica en multimedia no lo logrará.

Sáltate Apple TV si tu app es interactiva. El TV es para experiencias ambientales (un temporizador corriendo en una pantalla al otro lado de la sala, reproducción de música). Cualquier cosa que necesite entrada frecuente del usuario está peleando contra la plataforma. Return está en tvOS porque “pon un temporizador de 20 minutos y mira el fuego en la pantalla” es exactamente el caso ambiental adecuado. Una app para tomar notas sería miserable.

Sáltate Mac si tu app es una interfaz pensada primero para el teléfono. SwiftUI en Mac funciona, pero el modelo de push del NavigationStack se lee como de juguete comparado con una verdadera barra lateral de Mac. Si la app se sentiría poco trabajada en Mac, publica con Catalyst (que convierte la app de iPad) o sáltate Mac por completo hasta que puedas construir una UI nativa de Mac.

Sáltate iPad si no has hecho adaptación por size class. Una app de iPhone estirada para llenar un iPad se lee como barata. El iPad necesita como mínimo un NavigationSplitView con barra lateral; idealmente una verdadera disposición de dos paneles. Return usa split views en iPad y stacks en iPhone. El código está en el mismo target pero la UI es genuinamente distinta.

La regla que tracé: publica en una plataforma cuando la interacción central de la app coincida con el modelo de entrada de esa plataforma. Publica un temporizador de meditación en Apple Watch (un toque para empezar). Publica un temporizador de meditación en Apple TV (poner y olvidar). No publiques un tablero kanban en ninguno.

Lo que viaja sin esfuerzo

Las tres cosas que sí se compartieron entre las cinco plataformas en Return:

  1. El modelo de datos (MeditationSession). El struct es idéntico en cada plataforma, se sincroniza vía NSUbiquitousKeyValueStore, y cualquier plataforma puede leer lo que cualquier otra plataforma escribió.
  2. La vista de historial de sesiones (SessionHistoryView). Una List de sesiones pasadas se renderiza idénticamente en iPhone, iPad, Mac, Apple Watch y Apple TV. La List de SwiftUI es una de las pocas primitivas que se adapta limpiamente a los cinco factores de forma.
  3. El envoltorio de persistencia (SessionStore). Las lecturas y escrituras son agnósticas a la plataforma; el almacenamiento subyacente (NSUbiquitousKeyValueStore) es la misma API en todas partes.

Tres conceptos. Estado, renderizado de listas y persistencia. Cualquier cosa con estado y de presentación que no involucre un modelo de entrada específico del hardware es compartible. Cualquier cosa que toque entrada, foco, ruteo de audio, tamaño de pantalla o ejecución en segundo plano no lo es.

Este patrón aparece en la guía de Desarrollo de Agentes para iOS, donde argumenté lo mismo con palabras distintas: las partes de una app de iOS que un agente puede escribir comparten la mayor parte de su código con las partes que escribe un humano; las partes que requieren juicio humano (firma, pulido visual, rendimiento) son exactamente las partes que tampoco se comparten bien entre plataformas.9 Las dos fronteras se alinean. Ambas tratan sobre dónde empieza a importar el conocimiento del dominio.

Lo que cuesta ser multiplataforma

El ROI es asimétrico. Añadir iPad a una app de iPhone cuesta tal vez un 20% más de código (ramas para size class, split view en algunos lugares). Añadir Mac al mismo target añade otro 15-20% (ramas #if os(macOS), barra de menú, gestión de ventanas). Cada target principal añade alrededor de 10 archivos para una app pequeña.

Apple Watch y Apple TV son los costosos. Añadir watchOS a Return requirió 11 archivos nuevos en un target separado, incluyendo gestores dedicados de audio, temporizador y HealthKit. Añadir tvOS requirió 10 archivos nuevos en otro target separado, incluyendo gestión de foco y un selector de duración personalizado. Juntos casi duplicaron la superficie de Swift para lo que es, a nivel de funcionalidad para el usuario, la misma app.

La decisión de publicar en las cinco no fue “queremos ser multiplataforma porque sí”. Fue una serie de decisiones separadas: Apple Watch porque los temporizadores de meditación genuinamente pertenecen a la muñeca, Apple TV porque el formato de pantalla ambiental se ajusta a sesiones largas en una sala, Mac porque algunos usuarios meditan en su escritorio entre reuniones. Cada plataforma se ganó su target al tener un caso de uso real.

Si una funcionalidad no se gana su target, la jugada más barata es saltarse la plataforma y redoblar la apuesta en las plataformas donde la app es excelente.

Qué significa esto para tu app

Tres conclusiones.

  1. Por defecto, un target por grupo principal de plataformas. iOS + iPadOS + macOS en un solo target funciona porque la interacción central (táctil + cursor) es similar. tvOS en un target separado. watchOS en un target separado. Cada target separado cuesta unos 10 archivos pero te salva de una clase Dios con ramas #if que crecen sin límite.
  2. Comparte agresivamente el estado, no la interacción. Los structs de modelo Codable, los envoltorios de persistencia y los renderizados de List viajan casi gratis. Los gestores de temporizador, los gestores de audio y las vistas de contenido no.
  3. Gánate cada plataforma. No publiques en watchOS porque puedes. Publica cuando la interacción central de tu app coincida con el modelo de entrada de la plataforma. Sáltate el resto.

Este patrón funciona en conjunto con las otras tres superficies sobre las que he escrito para la misma familia de apps: App Intents tipados para Apple Intelligence, servidores MCP para agentes entre LLM, Liquid Glass para el humano frente al dispositivo. La capa más externa del mismo stack es la plataforma: en qué pantallas siquiera corre la app. Elige eso con la misma deliberación con la que eliges la superficie de IA.

Preguntas frecuentes

¿Por qué no usar un Swift package para el código compartido?

Lo consideré. Para tres archivos, un Swift package añade más ceremonia de la que ahorra. El sistema de build de Apple en Xcode 26 compila felizmente un mismo archivo fuente en múltiples targets cuando marcas las casillas de Target Membership. Un package añade un Package.swift separado, un target de tests separado, y un paso de indirección que cada refactor tiene que navegar. Para un núcleo compartido pequeño, gana la respuesta más simple.10

¿Funciona SwiftData en watchOS y tvOS?

SwiftData está disponible en iOS 17+, macOS 14+, watchOS 10+ y tvOS 17+, cubriendo todas las plataformas a las que apunta Return.11 El struct MeditationSession es Codable plano, no @Model, porque Return usa NSUbiquitousKeyValueStore para sincronizar el historial de sesiones en lugar de un contenedor de SwiftData. El patrón funciona del mismo modo para tipos @Model: el archivo del modelo se comparte, el contenedor de persistencia difiere por plataforma si tiene que hacerlo.

¿Debería usar Mac Catalyst o un target nativo de Mac?

Catalyst es la herramienta correcta cuando la app de iPad es lo suficientemente buena como para que una versión Mac reconstruida con Catalyst se lea como nativa. El target principal de Return es un target multiplataforma verdadero (no Catalyst), construido con SwiftUI para iOS, iPadOS y macOS en un solo binario. La UI de Mac usa #if os(macOS) para renderizar de manera distinta al iPad: barra lateral en lugar de sheet, equivalentes de tecla en los botones, etc. Catalyst habría sido más simple, pero la UI de Mac se habría visto como una app de iPad en una Mac, que es el modo de fallo por el que Catalyst es más conocido.

¿Vale la pena publicar en Apple TV para una app pequeña?

Probablemente no. Las apps de Apple TV tienen casos de uso muy específicos (ambiental, multimedia, juego casual). Si tu app no encaja en uno de esos, la audiencia de la plataforma es demasiado pequeña para justificar los 10 archivos Swift por app. Return apunta a tvOS específicamente porque las sesiones largas de meditación en una pantalla al otro lado de la sala son uno de los pocos casos de uso adyacentes a la productividad que encajan con la plataforma.

¿Cuánto se tarda en publicar en las cinco plataformas?

Difícil dar un número preciso; depende de la app. Return se publicó multiplataforma desde el primer día en lugar de añadir plataformas de forma incremental, lo cual es más rápido que adaptarlo después. Como regla aproximada: una MVP exclusiva de iPhone más soporte para iPad más soporte para Mac es aproximadamente 1,5 veces el tiempo solo de iPhone. Añadir Apple Watch suma otro 0,5 veces. Añadir Apple TV suma otro 0,5 veces. Así que un primer lanzamiento para cinco plataformas es aproximadamente 2,5 veces el esfuerzo solo de iPhone, con la salvedad de que esto fue una construcción asistida por agente donde la mayor parte del código duplicado fue editado en lote por Claude Code en lugar de tipeado a mano.

Referencias


  1. Return del autor, una app de temporizador de meditación publicada en la App Store el 21 de abril de 2026. Targets nativos: iOS 26+, iPadOS 26+, macOS 26+, watchOS 26+, tvOS 26+. SwiftUI en todo. NSUbiquitousKeyValueStore para el historial de sesiones entre dispositivos. 

  2. Apple Developer, “Configuring a Multi-Platform App” y la sesión “SwiftUI essentials” en la WWDC 2024. La guía por defecto de Apple se inclina hacia un solo target con adaptación impulsada por el entorno; la ruta de múltiples targets que toma este artículo es una desviación deliberada. 

  3. Código de producción en Return/Return/Shared/MeditationSession.swift, SessionStore.swift, SessionHistoryView.swift. El comentario de cabecera en MeditationSession.swift dice: “Add this file to: Return, ReturnTV, ReturnWatch Watch App targets.” 

  4. Código de producción en Return/Return/VideoBackgroundView.swift (8 ramas #if os(iOS) más una rama #elseif os(macOS)), Return/Return/ContentView.swift (10 ramas #if os), Return/Return/AudioManager.swift (6 ramas #if os), Return/Return/LiveActivityManager.swift (8 ramas #if os, archivo exclusivo de iOS). Los conteos de ramas se obtuvieron ejecutando grep -Ec '^\s*#if os\\(' <file>

  5. Apple Developer, “Focus interactions” Human Interface Guidelines. El motor de foco de tvOS es un modelo de navegación fundamentalmente distinto del táctil en iOS o del puntero en Mac. 

  6. Código de producción en Return/ReturnTV/TVFocusModifier.swift. Define dos tipos ButtonStyle (TVCapsuleButtonStyle y TVCircleButtonStyle) que envuelven @Environment(\.isFocused) para invertir el color y la translucidez al recibir el foco y aplicar escala más sombra. 

  7. Apple Developer, “WatchConnectivity”. El framework para la comunicación emparejada entre iPhone y Watch; Return no lo usa para la sincronización de sesiones, apoyándose en cambio en el iCloud key-value store. 

  8. Apple Developer, “WKExtendedRuntimeSession” y la clave del Info.plist “WKBackgroundModes”. El valor mindfulness está documentado como: “Enables extended runtime sessions for silent meditation” — el ajuste correcto para un temporizador de meditación. Return crea una WKExtendedRuntimeSession() por defecto y declara WKBackgroundModes: mindfulness en el Info.plist del target de watchOS. Código de producción: Return/ReturnWatch Watch App/ReturnWatchApp.swift define WatchSessionManager: NSObject, WKExtendedRuntimeSessionDelegate; WatchTimerManager.swift le delega el trabajo de tiempo de ejecución extendido. 

  9. Análisis del autor en Construyendo apps de iOS con agentes de IA, la guía práctica para el desarrollo iOS asistido por agentes a través de 8 apps en producción. 

  10. Apple Developer, “Configuring a Multi-Platform App”. Target Membership permite que un mismo archivo fuente compile en múltiples targets sin un Swift package. La herramienta correcta para núcleos compartidos pequeños. 

  11. Apple Developer, disponibilidad de plataforma de “SwiftData”. Disponible en iOS 17+, iPadOS 17+, macOS 14+, watchOS 10+, tvOS 17+, visionOS 1+, cubriendo las cinco familias de plataformas Apple. 

  12. Apple Developer, “NSUbiquitousKeyValueStore”. El iCloud Key-Value Store de Apple para sincronizar pequeñas cantidades de estado entre los dispositivos de un usuario. El tamaño total del almacén está limitado a 1 MB sobre todas las claves según los límites publicados por Apple. Código de producción: Return/Return/Shared/SessionStore.swift

  13. Apple Developer, EnvironmentValues.isFocused. Disponible en iOS 14+, iPadOS 14+, macOS 11+, tvOS 14+, watchOS 7+. La API es multiplataforma; lo que difiere es si el foco es la principal forma de navegación del usuario. 

Artículos relacionados

La matriz de plataformas de Apple: qué objetivos merecen qué app

iOS, iPad, Mac, Watch, Vision, TV. Seis plataformas, seis obligaciones. Elegir los objetivos de Apple es una decisión de…

19 min de lectura

HealthKit + SwiftUI en iOS 26: autorización, tipos de muestra y patrones multiplataforma de dos apps en producción

Patrones reales de producción de Water (seguimiento de hidratación, HKQuantitySample) y Return (sesiones de mindfulness,…

16 min de lectura

Ingeniería de bucles: los bucles ganan donde verificar es barato

La ingeniería de bucles, contrastada con las transcripciones completas de Boris Cherny: cada bucle que menciona tiene un…

18 min de lectura