← Todos los artículos

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

La mayoría de las apps que llegan a visionOS lo hacen por la vía de compatibilidad “Designed for iPad” de Apple: el binario existente de iPad corre como un panel plano suspendido en el espacio 3D, y quien desarrolla marca una casilla en lugar de construir una experiencia nativa de visionOS. Para el usuario no hay problema (la app funciona), pero deja la plataforma a medio aprovechar. La superficie nativa de visionOS ofrece tres métodos de presentación (Windows, Volumes e Immersive Spaces) más primitivas estructurales de UI (Ornaments, Attachments) que el SDK de iPad no tiene.4 Las apps que los adoptan resultan nativas; las que no, se leen como iPad-sobre-Vision.

Este artículo recorre el vocabulario espacial contrastándolo con la documentación de Apple. El enfoque es “qué ofrece realmente la plataforma a una app de SwiftUI”, no una introducción a visionOS. El artículo del clúster sobre RealityKit y el modelo mental espacial cubre la capa de contenido 3D; este cubre la superficie SwiftUI que la contiene.

En resumen

  • Las apps de visionOS componen tres tipos de escena: WindowGroup (Windows), WindowGroup con .windowStyle(.volumetric) (Volumes) e ImmersiveSpace (Immersive Spaces)1.
  • Una Window es un plano 2D; un Volume es una región 3D acotada; un Immersive Space rodea al usuario. Cada uno tiene reglas distintas: los Volumes tienen tamaño inmutable después de crearse, los Immersive Spaces exigen apertura y cierre explícitos, y las Windows son lo más parecido a iPad.
  • La inmersión viene en tres estilos: .mixed (el contenido convive con la habitación), .full (la habitación se reemplaza por un entorno virtual) y .progressive (punto intermedio que conserva el anclaje periférico)2.
  • Los Ornaments son planos de UI paralelos a una Window y adelantados en el eje z. Así resuelve visionOS las barras de herramientas y las barras de pestañas3. Los Attachments incrustan vistas 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 interfaz de iPad como una Window sin adoptar Volume, Space ni Ornament. El usuario puede usar la app, pero el valor real de la plataforma queda sin aprovechar.

Los tres tipos de escena

El cuerpo App de una app de visionOS compone escenas a partir de tres clases, y cada una implica un modelo mental distinto para el usuario.

Windows: el plano 2D

WindowGroup produce una Window 2D con el marco de vidrio de visionOS por defecto. La Window se posiciona en el espacio (el sistema la coloca frente al punto donde mira el usuario) y el usuario la mueve o redimensiona con los gestos estándar del sistema. Desde el punto de vista de SwiftUI, una Window es el análogo en visionOS de una ventana de macOS: una superficie de contenido plana con un material de vidrio que responde a la profundidad.

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

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

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

Las Windows de estilo plain pierden el marco de vidrio del sistema. Úsalas cuando el contenido aporte su propio contenedor visual; en cualquier otro caso, lo correcto es el estilo por defecto.

Volumes: la región 3D acotada

Un Volume es una región 3D que aloja contenido con profundidad: un modelo, una escena con varios objetos, una interfaz que gana con un tercer eje. La escena volumétrica también es un WindowGroup, con otro estilo:

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:) define los límites del volumen en unidades del mundo real (metros). Por defecto esos límites quedan fijos al abrirse, y el usuario puede mover el volumen pero no redimensionarlo. visionOS 2+ agregó una vía opcional mediante .windowResizability(.contentSize) y APIs relacionadas para las apps que quieren volúmenes redimensionables por el usuario; el tamaño fijo por defecto sigue siendo el caso más común. La consecuencia práctica: elige con cuidado el tamaño por defecto, porque la mayoría de los volúmenes no son redimensionables salvo que quien desarrolla lo habilite de forma explícita.

Los buenos candidatos para un Volume son las apps donde el límite espacial forma parte de la experiencia: una escultura virtual que el usuario rodea caminando, una cinta métrica anclada a una pared real, una escena de entrenamiento con objetivos escalonados en profundidad. Las apps que solo quieren un lienzo más ancho no ganan nada con un Volume; ahí la respuesta correcta es una Window más grande.

Immersive Spaces: el entorno envolvente

Un ImmersiveSpace es una escena que ocupa el entorno alrededor del usuario.5 A diferencia de una Window o un Volume (ambos visibles junto a otras apps en el Shared Space), un Immersive Space se apodera del entorno del usuario e impide usar al mismo tiempo 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 en apps donde el usuario se beneficia de ambos contextos.
  • .progressive. Una inmersión parcial que se regula con la Digital Crown. El usuario conserva 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 en experiencias plenamente inmersivas (meditación, simulaciones de entrenamiento, juegos).

Abrir un Immersive Space es una acción explícita. La app llama a @Environment(\.openImmersiveSpace) con el id del espacio; el sistema se encarga de la animación de transición y de cerrar cualquier espacio en conflicto:

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

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

Solo puede haber un Immersive Space activo a la vez por app. Pasar de un Space a otro (de .mixed a .full, por ejemplo) exige cerrar explícitamente el anterior y abrir el nuevo.

Ornaments: los planos de UI alrededor de una Window

Los Ornaments son vistas SwiftUI adheridas al borde de una Window, situadas ligeramente por delante del plano de la Window en el eje z. Así resuelve visionOS 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: indica dónde se sitúa el ornament respecto de la Window: .scene(.top), .scene(.bottom), .scene(.leading), .scene(.trailing). El tratamiento visual del ornament corre por cuenta de quien desarrolla; .glassBackgroundEffect() produce el material de vidrio nativo de visionOS que combina con el marco de la Window.

Los Ornaments resuelven un problema real en visionOS: meter los controles dentro de la Window satura el contenido, y ponerlos en una Window aparte obliga al usuario a reorientar la mirada. Un ornament flota en la visión periférica del usuario, se puede enfocar con la mirada y no compite con el contenido principal por la vista central.

Attachments de RealityView: SwiftUI dentro del espacio 3D

Cuando una app necesita vistas SwiftUI dentro de una escena 3D (una etiqueta sobre un modelo, un botón suspendido cerca de un objeto virtual, una lectura de medición anclada 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 closure attachments: declara vistas SwiftUI con identificadores estables. Dentro del closure 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 del ciclo de actualización de SwiftUI (los cambios de estado la redibujan) mientras se renderiza como un plano texturizado dentro de la escena 3D.

El mecanismo es el adecuado para cualquier UI situada en el mundo: una etiqueta que sigue a un objeto en movimiento, una anotación de medición, un botón contextual. La forma de escribir la vista 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 por compatibilidad “Designed for iPad” y sale como una sola Window, sin Volume, sin Immersive Space y sin Ornaments. La app funciona, pero no se gana la plataforma.

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

Una única escena de tipo Window. Nada de .windowStyle(.volumetric), ningún ImmersiveSpace declarado. La app es una superficie plana y nada más.

Ningún ornament adoptado. La barra de pestañas vive dentro del contenido de la Window en lugar de fuera. El resultado se ve más recargado que una app nativa de visionOS con la misma densidad de contenido.

Ninguna función exclusivamente espacial. La app no usa el tercer eje para nada: ni modelos 3D en un Volume, ni escena ambiental en un Space, ni UI posicionada en z mediante attachments. La app hace lo mismo que hacía en iPad, solo que flotando.

Las apps panel no son un fracaso; son la decisión correcta para categorías de contenido que no ganan nada con la computación espacial (una app de chat, una de notas, una utilidad de configuración). El modo de fallo es publicar una app panel y atribuirle autoridad nativa en visionOS. El artículo del clúster sobre la matriz de plataformas de Apple sostiene que incluir una plataforma es una decisión de producto; en visionOS, la decisión es: “¿esta app debería ganarse la superficie espacial, o basta con el panel?”.

Fallos habituales

Tres patrones que producen una mala UX en visionOS:

Volumes que en realidad son contenido 2D con relleno de profundidad. Una interfaz “3D” que llena un Volume pero dibuja superficies planas por dentro desperdicia el espacio disponible. Los Volumes son para contenido 3D; el contenido plano pertenece a una Window.

Un estilo de inmersión que pelea con el caso de uso. Una app de meditación que solo ofrece inmersión .full saca al usuario de su entorno para sesiones cortas. Una app de entrenamiento que solo ofrece .mixed se queda corta para ejercicios de concentración plena. Ajusta 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 reclama atención central (un color parpadeante, movimiento animado) frustra su propósito. Usa los ornaments para controles estables, de lectura rápida.

Qué implica 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 resulte fácil. Una lista plana de elementos es una Window. Un modelo 3D que el usuario inspecciona es un Volume. Un entorno envolvente es un Immersive Space. Combinarlos en una misma app (una Window con un Volume que se abre bajo demanda, un Immersive Space accesible desde un botón de la Window) es el patrón nativo de visionOS.

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

  3. Usa attachments para la UI situada en el mundo dentro de RealityView. Etiquetas sobre objetos 3D, botones junto a contenido virtual, lecturas contextuales. El puente entre SwiftUI y el espacio 3D ya está resuelto; el modo de fallo es no usarlo y terminar renderizando texto 3D a mano.

El clúster completo del ecosistema Apple: App Intents tipados; servidores MCP; la pregunta del enrutamiento; Foundation Models; la distinción entre LLM de ejecución y de herramientas; tres superficies; el patrón de fuente única de verdad; Dos servidores MCP; hooks para desarrollo en Apple; Live Activities; el entorno de ejecución de watchOS; las entrañas de SwiftUI; el modelo mental espacial de RealityKit; disciplina de esquemas en SwiftData; patrones de Liquid Glass; publicar en varias plataformas; la matriz de plataformas; el framework Vision; Symbol Effects; inferencia en el dispositivo con Core ML; adopción de la API de Writing Tools; Swift Testing; Privacy Manifest a fondo; la accesibilidad como plataforma; la tipografía SF Pro; sobre qué me niego a escribir. El hub está en la serie Ecosistema Apple. Para un contexto más amplio de iOS con agentes de IA, mira la guía de desarrollo iOS con agentes.

Preguntas frecuentes

¿Cuál es la diferencia entre un Volume y un Immersive Space?

Un Volume es una región 3D acotada que vive en el Shared Space junto a otras apps. El usuario puede rodearla caminando, el sistema la enmarca y las ventanas de las demás apps siguen visibles. Un Immersive Space rodea al usuario, se apodera del entorno e impide usar otras apps al mismo tiempo. Los Volumes son para “mira esta cosa 3D”; los Spaces, para “estar dentro de este entorno”.

¿Puedo abrir varios Volumes a la vez?

Sí. Pueden estar abiertas al mismo tiempo 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 Immersive Spaces a la vez?

No. Solo puede haber un Immersive Space activo por app a la vez. Cambiar de un Space a otro exige cerrar explícitamente el actual y abrir el nuevo mediante @Environment(\.openImmersiveSpace) y @Environment(\.dismissImmersiveSpace).

¿El tamaño de un Volume es realmente inmutable?

Los límites de un Volume quedan fijos al abrirse por defecto; el planteamiento de la HIG de visionOS es que los Volumes representan contenido 3D específico con límites intencionales, y que un redimensionamiento arbitrario del usuario distorsionaría la escala prevista del contenido. visionOS 2+ agregó una opción para quien desarrolla que habilita volúmenes redimensionables vía .windowResizability(.contentSize) y APIs relacionadas, así que las apps que necesitan contenedores espaciales redimensionables pueden solicitarlo. La mayoría de los volúmenes se publican con el valor fijo por defecto, que la HIG sigue recomendando para contenido con una escala específica (una escultura virtual, un modelo de tamaño físico).

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

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

¿Los attachments de RealityView pueden interactuar con el seguimiento de manos?

Sí. Una vez posicionados, los attachments son entidades 3D y participan del mismo sistema de gestos y detección de impactos que las demás entidades de RealityKit. Los gestos de toque, arrastre y hover se les adhieren mediante los modificadores de gestos estándar de SwiftUI; el artículo sobre RealityKit del clúster cubre los patrones de integración con el seguimiento de manos.

Referencias


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

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

  3. Documentación para desarrolladores de Apple: ornament(visibility:attachmentAnchor:contentAlignment:ornament:). El modificador de vista de SwiftUI que agrega un plano de UI de tipo ornament a una Window con el anclaje indicado. 

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

  5. Documentación para desarrolladores de Apple: 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 y el modelo mental espacial

RealityKit es un sistema entidad-componente, no SwiftUI en 3D: los anclajes ubican entidades en el espacio real. Cinco d…

18 min de lectura

Novedades de visionOS 27 para desarrolladores espaciales

visionOS 27 añade Spatial Preview desde el Mac, Foveated Streaming desde un PC, entornos inmersivos en Safari y seguimie…

19 min de lectura

Instalar y actualizar Codex CLI: Mac, Linux, Windows

Todas las formas de instalar, actualizar, fijar versión y desinstalar la CLI de OpenAI Codex -- script de instalación, n…

20 min de lectura