← Todos los articulos

Patrones espaciales de visionOS más allá de la ventana

La mayoría de las apps que se publican en visionOS llegan a la plataforma a través de la ruta de compatibilidad “Designed for iPad” de Apple: el binario existente del iPad se ejecuta como un panel plano flotando en el espacio 3D, y el desarrollador marca una casilla en lugar de construir una experiencia nativa de visionOS. La ruta está bien para el usuario (la app funciona) pero subestima la plataforma. La superficie nativa de visionOS le da a los desarrolladores tres métodos de presentación (Ventanas, Volúmenes y Espacios Inmersivos) además de primitivas estructurales de UI (Ornaments, Attachments) que el SDK del iPad no tiene. Las apps que las adoptan se sienten nativas; las que no, se leen como iPad sobre Vision.

Esta publicación recorre el vocabulario espacial contrastándolo con la documentación de Apple. El marco es “qué le ofrece realmente la plataforma a una app de SwiftUI” más que una introducción a visionOS. El post del cluster RealityKit y el modelo mental espacial cubre la capa de contenido 3D; este post cubre la superficie de SwiftUI que la contiene.

TL;DR

  • Las apps de visionOS componen tres tipos de escenas: WindowGroup (Ventanas), WindowGroup con .windowStyle(.volumetric) (Volúmenes) y ImmersiveSpace (Espacios Inmersivos)1.
  • Una Ventana es un plano 2D; un Volumen es una región 3D acotada; un Espacio Inmersivo rodea al usuario. Cada uno tiene reglas distintas: los Volúmenes tienen un tamaño inmutable después de la creación, los Espacios Inmersivos requieren apertura y cierre explícitos, las Ventanas se comportan de forma muy similar al iPad.
  • La inmersión viene en tres estilos: .mixed (el contenido coexiste con la habitación), .full (la habitación se reemplaza por un entorno virtual), .progressive (un punto intermedio con anclaje periférico)2.
  • Los ornaments son planos de UI paralelos a una Ventana y adelantados en el eje z. Es como visionOS implementa las barras de herramientas y las barras de pestañas3. Los attachments incrustan vistas de SwiftUI dentro de contenido 3D en un RealityView, el puente entre la UI plana y la geometría espacial.
  • El antipatrón de la “app panel”: publicar la UI del iPad como una Ventana sin adoptar Volumen, Espacio ni Ornament. El usuario puede usar la app, pero el verdadero valor de la plataforma queda sin reclamar.

Los tres tipos de escenas

El cuerpo App de una app de visionOS compone escenas a partir de tres clases. Cada una tiene un modelo mental distinto para el usuario.

Ventanas: el plano 2D

WindowGroup produce una Ventana 2D con el marco de cristal de visionOS por defecto. La Ventana se posiciona en el espacio (el sistema la coloca frente a donde el usuario está mirando) y el usuario la mueve o la redimensiona mediante los gestos estándar del sistema. Desde el punto de vista de SwiftUI, una Ventana es el equivalente en visionOS de una ventana de macOS: una superficie de contenido plana con un material de cristal consciente de la profundidad.

@main
struct MyApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

La Ventana por defecto tiene un material de cristal alrededor de su contenido. Las apps que quieren una superficie totalmente transparente usan .windowStyle(.plain):

WindowGroup {
    ContentView()
}
.windowStyle(.plain)

Las Ventanas de estilo plain pierden el marco de cristal del sistema. Úsalas cuando el contenido aporta su propio contenedor visual; de lo contrario, el predeterminado es lo correcto.

Volúmenes: la región 3D acotada

Un Volumen es una región 3D que contiene contenido consciente de la profundidad (un modelo, una escena con varios objetos, una UI que se beneficia de un tercer eje). La escena de volumen también es un WindowGroup, con un estilo distinto:

WindowGroup(id: "globe") {
    GlobeView()
}
.windowStyle(.volumetric)
.defaultSize(width: 0.6, height: 0.6, depth: 0.6, in: .meters)

El modificador .defaultSize(width:height:depth:in:) especifica los límites del volumen en unidades del mundo real (metros). Por defecto los límites quedan fijos al abrirse, y el usuario puede mover el volumen pero no redimensionarlo. visionOS 2+ añadió una ruta opt-in mediante .windowResizability(.contentSize) y APIs relacionadas para apps que quieren volúmenes redimensionables por el usuario; el caso por defecto de tamaño fijo sigue siendo el más común. La implicación: elige el tamaño por defecto con cuidado, porque la mayoría de los volúmenes no son redimensionables a menos que el desarrollador lo habilite explícitamente.

Los buenos candidatos para Volúmenes son las apps donde el límite espacial es parte de la experiencia: una escultura virtual alrededor de la cual el usuario camina, una cinta métrica fijada a una pared real, una escena de entrenamiento con objetivos escalonados en profundidad. Las apps que solo quieren un lienzo más amplio no ganan nada con un Volumen; una Ventana más grande es la respuesta correcta.

Espacios Inmersivos: el envolvente

Un ImmersiveSpace es una escena que ocupa el entorno alrededor del usuario. A diferencia de una Ventana o un Volumen (ambos visibles junto con otras apps en el Espacio Compartido), un Espacio Inmersivo se apropia del entorno del usuario y bloquea el uso simultáneo de las ventanas de otras apps.

@main
struct MyApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }

        ImmersiveSpace(id: "training") {
            TrainingScene()
        }
        .immersionStyle(selection: .constant(.mixed), in: .mixed, .progressive, .full)
    }
}

El modificador .immersionStyle(...) elige el nivel de experiencia:

  • .mixed. El contenido virtual aparece junto a la habitación real. Se usa para apps donde el usuario se beneficia de ambos contextos.
  • .progressive. Una inmersión parcial que usa la Digital Crown para subir o bajar. El usuario mantiene la conciencia periférica de la habitación mientras la vista central es virtual.
  • .full. La habitación se reemplaza por un entorno virtual. Se usa para experiencias completamente inmersivas (meditación, simulaciones de entrenamiento, juegos).

Abrir un Espacio Inmersivo es explícito. La app llama a @Environment(\.openImmersiveSpace) con el id del espacio; el sistema gestiona la animación de transición y el cierre de cualquier espacio que entre en conflicto:

@Environment(\.openImmersiveSpace) var openImmersiveSpace
@Environment(\.dismissImmersiveSpace) var dismissImmersiveSpace

Button("Start Session") {
    Task {
        await openImmersiveSpace(id: "training")
    }
}

Solo puede haber un Espacio Inmersivo activo a la vez por app. La transición entre Espacios (de .mixed a .full, por ejemplo) requiere el cierre explícito del Espacio anterior y la apertura del nuevo.

Ornaments: los planos de UI alrededor de una Ventana

Los ornaments son vistas de SwiftUI adheridas al borde de una Ventana, posicionadas ligeramente delante del plano de la Ventana en el eje z. Es como visionOS implementa las barras de herramientas, las barras de pestañas y los controles accesorios. El sistema los usa por todas partes: los controles de reproducción en TV, el control segmentado en Music, la barra de herramientas en Mail.

ContentView()
    .ornament(
        attachmentAnchor: .scene(.bottom),
        contentAlignment: .center
    ) {
        HStack {
            Button("Previous", systemImage: "backward.fill") { ... }
            Button("Play", systemImage: "play.fill") { ... }
            Button("Next", systemImage: "forward.fill") { ... }
        }
        .padding()
        .glassBackgroundEffect()
    }

El parámetro attachmentAnchor: especifica dónde se ubica el ornament respecto a la Ventana: .scene(.top), .scene(.bottom), .scene(.leading), .scene(.trailing). El tratamiento visual del ornament es responsabilidad del desarrollador; .glassBackgroundEffect() produce el material de cristal nativo de visionOS que combina con el marco de la Ventana.

Los ornaments resuelven un problema real en visionOS: poner los controles dentro de la Ventana satura el contenido; ponerlos en una Ventana separada obliga al usuario a redirigir la mirada. Un ornament flota en la visión periférica del usuario, es alcanzable con la mirada, pero no compite con el contenido principal por la vista central.

Attachments de RealityView: SwiftUI dentro del espacio 3D

Cuando una app necesita vistas de SwiftUI dentro de una escena 3D (una etiqueta sobre un modelo 3D, un botón flotando cerca de un objeto virtual, una lectura de medición fijada a una superficie del mundo real), el puente es el mecanismo de attachments de RealityView.

RealityView { content, attachments in
    let model = ModelEntity(...)
    content.add(model)

    if let label = attachments.entity(for: "label") {
        label.position = [0, 0.5, 0]
        model.addChild(label)
    }
} attachments: {
    Attachment(id: "label") {
        Text("Vintage Globe, 1872")
            .padding()
            .glassBackgroundEffect()
    }
}

El cierre attachments: declara vistas de SwiftUI con identificadores estables. Dentro del cierre principal de RealityView, attachments.entity(for:) recupera la vista como una Entity 3D que puede posicionarse en el espacio de coordenadas de la escena. La vista participa en el ciclo de actualización de SwiftUI (los cambios de estado redibujan la vista) mientras se renderiza como un plano texturizado en la escena 3D.

El mecanismo es el adecuado para cualquier UI dentro del mundo: una etiqueta que sigue a un objeto en movimiento, una anotación de medición, un botón contextual. La autoría de la vista de SwiftUI no cambia; el posicionamiento 3D ocurre en la capa de RealityView.

El antipatrón de la “app panel”

El error más común al publicar en visionOS es la app panel: una app de iPad que llega a visionOS a través de la compatibilidad “Designed for iPad” y se publica como una sola Ventana sin Volumen, sin Espacio Inmersivo y sin Ornaments. La app funciona, pero no se gana la plataforma.

Tres señales de que una app es una app panel:

Una sola escena de Ventana. No hay .windowStyle(.volumetric), no hay ImmersiveSpace declarado. La app es una superficie plana y eso es todo.

Sin ornaments adoptados. La barra de pestañas de la app vive dentro del contenido de la Ventana en lugar de fuera. El resultado es más saturado que una app nativa de visionOS con la misma densidad de contenido.

Sin funciones exclusivamente espaciales. La app no usa el tercer eje para nada: no hay modelos 3D en un Volumen, no hay escena ambiental en un Espacio, no hay UI con posicionamiento en z mediante attachments. La app hace lo mismo que hacía en iPad, solo que flotando.

Las apps panel no son fracasos; son el movimiento correcto para categorías de contenido que no se benefician de la computación espacial (una app de chat, una app de notas, una utilidad de configuración). El modo de fallo es publicar una app panel y reclamar autoridad nativa de visionOS para ella. El post Apple Platform Matrix del cluster argumenta que la inclusión en una plataforma es una decisión de producto; para visionOS, la decisión es “¿debería esta app ganarse la superficie espacial, o el panel es suficiente?”

Fallos comunes

Tres patrones que producen una mala UX de visionOS:

Volúmenes que en realidad son contenido 2D con relleno de profundidad. Una UI “3D” que llena un Volumen pero renderiza planos planos en su interior produce un desperdicio de espacio. Los Volúmenes son para contenido 3D; el contenido plano pertenece a una Ventana.

Estilo de inmersión que choca con el caso de uso. Una app de meditación que solo se publica con inmersión .full saca al usuario de su entorno para sesiones cortas. Una app de entrenamiento que solo se publica con .mixed no llega lo suficientemente lejos para ejercicios totalmente concentrados. Adapta el estilo de inmersión a la sesión real del usuario.

Ornaments que compiten con el contenido. Los ornaments son periféricos por diseño. Un ornament que exige atención central (un color parpadeante, un movimiento animado) anula su propósito. Usa los ornaments para controles estables y de un vistazo.

Qué significa este patrón para las apps de visionOS

Tres conclusiones.

  1. Elige el tipo de escena según el modelo mental del usuario, no según lo que sea fácil. Una lista plana de elementos es una Ventana. Un modelo 3D que el usuario inspecciona es un Volumen. Un entorno envolvente es un Espacio Inmersivo. Mezclarlos en una sola app (una Ventana con un Volumen que se abre bajo demanda, un Espacio Inmersivo accesible desde un botón de una Ventana) es el patrón nativo de visionOS.

  2. Adopta los ornaments para las barras de herramientas y la UI accesoria. Los ornaments son la forma en que visionOS comunica “esta UI es complementaria”; poner las barras de herramientas dentro del contenido de la Ventana se lee como iPad sobre Vision. La integración es pequeña y la diferencia visual es grande.

  3. Usa attachments para la UI dentro del mundo en RealityView. Etiquetas sobre objetos 3D, botones cerca de contenido virtual, lecturas contextuales. El puente entre SwiftUI y el espacio 3D ya está resuelto; el modo de fallo es no usarlo y terminar con un renderizado de texto 3D ad-hoc en su lugar.

El cluster completo del Ecosistema Apple: App Intents tipados; servidores MCP; la pregunta del enrutamiento; Foundation Models; la distinción runtime vs LLM de herramientas; tres superficies; el patrón de fuente única de verdad; Dos servidores MCP; hooks para desarrollo en Apple; Live Activities; el contrato de runtime de watchOS; internals de SwiftUI; el modelo mental espacial de RealityKit; disciplina de esquemas en SwiftData; patrones de Liquid Glass; publicar en múltiples plataformas; la matriz de plataformas; el framework Vision; Symbol Effects; inferencia con Core ML; adopción de la API de Writing Tools; Swift Testing; Privacy Manifest; Accesibilidad como plataforma; tipografía SF Pro; sobre qué me niego a escribir. El hub está en la Serie del Ecosistema Apple. Para un contexto más amplio sobre iOS con agentes de IA, consulta la guía de desarrollo de agentes para iOS.

FAQ

¿Cuál es la diferencia entre un Volumen y un Espacio Inmersivo?

Un Volumen es una región 3D acotada que vive en el Espacio Compartido junto a otras apps. El usuario puede caminar a su alrededor, el sistema lo enmarca, y las Ventanas de otras apps siguen siendo visibles. Un Espacio Inmersivo rodea al usuario, se apropia del entorno y previene el uso simultáneo de otras apps. Los Volúmenes son para “mira esta cosa 3D”; los Espacios son para “estar dentro de este entorno”.

¿Puedo abrir varios Volúmenes al mismo tiempo?

Sí. Pueden estar abiertas simultáneamente varias escenas WindowGroup con .volumetric, cada una con su propio tamaño y contenido. El sistema las posiciona de forma independiente en el espacio.

¿Puedo abrir varios Espacios Inmersivos al mismo tiempo?

No. Solo puede haber un Espacio Inmersivo activo por app a la vez. Cambiar entre Espacios requiere el cierre explícito del actual y la apertura del nuevo mediante @Environment(\.openImmersiveSpace) y @Environment(\.dismissImmersiveSpace).

¿El tamaño del Volumen es realmente inmutable?

Los límites del Volumen están fijados al abrirse por defecto; el marco de la HIG de visionOS es que los Volúmenes representan contenido 3D específico con límites intencionales, y el redimensionamiento arbitrario por parte del usuario distorsionaría la escala prevista del contenido. visionOS 2+ añadió un opt-in del desarrollador para volúmenes redimensionables mediante .windowResizability(.contentSize) y APIs relacionadas, así que las apps que necesitan contenedores espaciales redimensionables por el usuario pueden solicitarlo. La mayoría de los volúmenes se publican con el predeterminado fijo, que la HIG sigue recomendando para contenido con escala específica (una escultura virtual, un modelo de tamaño físico).

¿Cómo agrego una barra de pestañas a una Ventana de visionOS?

Usa un TabView dentro de la Ventana para pestañas dentro del contenido (el patrón estilo iPad), o usa un ornament con filas de botones personalizadas para una UI de pestañas periférica nativa de visionOS. La ruta del ornament es la que usan las propias apps de Apple (Music, Mail) y la que se siente más nativa para los usuarios de visionOS.

¿Pueden los attachments de RealityView interactuar con el seguimiento de manos?

Sí. Los attachments son entidades 3D una vez posicionados, y participan en el mismo sistema de gestos y de hit-testing que otras entidades de RealityKit. Los gestos de tap, drag y hover se les adhieren mediante los modificadores estándar de gestos de SwiftUI; el post de RealityKit del cluster cubre los patrones de integración del seguimiento de manos.

Referencias


  1. Apple Developer: Meet SwiftUI for spatial computing (sesión 10109 de WWDC 2023). Introducción de WindowGroup, WindowGroup volumétrico e ImmersiveSpace como los tres tipos de escena de visionOS. 

  2. Documentación de Apple Developer: ImmersionStyle. Los tres estilos de inmersión (.mixed, .progressive, .full) y el modificador .immersionStyle(selection:in:) API. 

  3. Documentación de Apple Developer: ornament(visibility:attachmentAnchor:contentAlignment:ornament:). El modificador de vista de SwiftUI que añade un plano de UI ornament a una Ventana con el ancla especificada. 

  4. Apple Developer: Go beyond the window with SwiftUI (sesión 10111 de WWDC 2023). La sesión que cubre Volúmenes, Espacios Inmersivos y los patrones para ir más allá de la UI de panel plano en visionOS. 

  5. Documentación de Apple Developer: Creating an immersive space in visionOS with SwiftUI. La guía de extremo a extremo para definir y abrir espacios inmersivos. 

Artículos relacionados

RealityKit And The Spatial Mental Model

RealityKit is an entity-component-system, not SwiftUI in 3D. Anchors place entities in real space. Five ways the model d…

16 min de lectura

HealthKit + SwiftUI on iOS 26: Authorization, Sample Types, and Cross-Platform Patterns

Real production patterns from Water (water tracking, HKQuantitySample) and Return (mindful sessions, HKCategorySample). …

17 min de lectura

The Cleanup Layer Is the Real AI Agent Market

Charlie Labs pivoted from building agents to cleaning up after them. The AI agent market is moving from generation to pr…

15 min de lectura