visionOS en Swift: RealityView, Immersive Spaces, Entity y programación espacial

visionOS es el sistema operativo de Apple Vision Pro, disponible desde 2024. Combina SwiftUI con RealityKit para crear experiencias espaciales que mezclan objetos 3D con la interfaz 2D habitual de una app. La curva de aprendizaje es menor de lo que parece: la mayor parte de SwiftUI funciona igual, y el espacio 3D se añade de forma incremental.

El modelo de espacio en visionOS

visionOS define tres modos de experiencia:

  • Window: ventana 2D flotante, idéntica a una app macOS o iOS.
  • Volume: caja 3D con contenido RealityKit visible desde todos los ángulos.
  • Immersive Space: el usuario entra en un espacio que puede ocupar todo el campo de visión.
import SwiftUI
import RealityKit

@main
struct MiAppVision: App {
    var body: some Scene {
        // Ventana estándar
        WindowGroup {
            ContentView()
        }

        // Volumen 3D
        WindowGroup(id: "cubo3d") {
            VistaVolumen()
        }
        .windowStyle(.volumetric)
        .defaultSize(width: 0.4, height: 0.4, depth: 0.4, in: .meters)

        // Espacio inmersivo
        ImmersiveSpace(id: "espacio") {
            EspacioInmersivo()
        }
        .immersionStyle(selection: .constant(.mixed), in: .mixed, .progressive, .full)
    }
}

RealityView: objetos 3D en SwiftUI

RealityView es el puente entre SwiftUI y RealityKit. Permite crear y actualizar entidades 3D como si fueran vistas SwiftUI:

struct VistaEsfera: View {
    var body: some View {
        RealityView { contenido in
            // Crear una esfera
            let mesh = MeshResource.generateSphere(radius: 0.1)
            let material = SimpleMaterial(color: .blue, isMetallic: true)
            let esfera = ModelEntity(mesh: mesh, materials: [material])
            esfera.position = [0, 0, -0.5]  // 50 cm frente al usuario
            contenido.add(esfera)
        } update: { contenido in
            // Actualizar cuando cambia el estado de SwiftUI
        }
        .gesture(
            TapGesture()
                .targetedToAnyEntity()
                .onEnded { valor in
                    // Manejar toque en cualquier entidad
                    valor.entity.scale *= 1.2
                }
        )
    }
}

Cargar modelos USDZ

Los modelos 3D en visionOS usan el formato USDZ. Se pueden cargar desde el bundle o desde la red:

RealityView { contenido in
    // Cargar modelo del bundle
    if let modelo = try? await ModelEntity(named: "silla.usdz") {
        modelo.position = [0, 0, -1.0]
        modelo.scale = [0.5, 0.5, 0.5]

        // Añadir componente para gestos
        modelo.components.set(InputTargetComponent())
        modelo.generateCollisionShapes(recursive: true)

        contenido.add(modelo)
    }
}

ImmersiveSpace: experiencias inmersivas

Un ImmersiveSpace puede estar en tres modos: .mixed (objetos 3D mezclados con el entorno real), .progressive (el usuario controla cuánto ocluye el entorno) y .full (entorno completamente virtual):

struct EspacioInmersivo: View {
    var body: some View {
        RealityView { contenido in
            // Suelo virtual
            let suelo = ModelEntity(
                mesh: .generatePlane(width: 10, depth: 10),
                materials: [SimpleMaterial(color: .init(white: 0.1, alpha: 0.5), isMetallic: false)]
            )
            suelo.position = [0, -1, 0]
            contenido.add(suelo)

            // Partículas o elementos del entorno
            for i in 0..<20 {
                let bola = ModelEntity(
                    mesh: .generateSphere(radius: 0.05),
                    materials: [SimpleMaterial(color: .random, isMetallic: true)]
                )
                bola.position = [Float.random(in: -3...3), Float.random(in: 0...2), Float.random(in: -3...0)]
                contenido.add(bola)
            }
        }
    }
}

// Abrir el espacio inmersivo desde una ventana
struct ContentView: View {
    @Environment(.openImmersiveSpace) var openImmersiveSpace
    @Environment(.dismissImmersiveSpace) var dismissImmersiveSpace

    var body: some View {
        Button("Entrar en el espacio") {
            Task { await openImmersiveSpace(id: "espacio") }
        }
    }
}

Attachments: mezclar SwiftUI con RealityKit

Los attachments permiten anclar vistas SwiftUI a entidades de RealityKit, muy útil para etiquetas, menús contextuales o paneles de información flotantes:

RealityView { contenido, attachments in
    let planeta = ModelEntity(mesh: .generateSphere(radius: 0.15),
                               materials: [SimpleMaterial(color: .blue, isMetallic: false)])
    planeta.name = "tierra"
    planeta.position = [0, 0.5, -1]
    contenido.add(planeta)

    // Adjuntar la etiqueta SwiftUI al planeta
    if let etiqueta = attachments.entity(for: "etiqueta-tierra") {
        etiqueta.position = [0, 0.2, 0]
        planeta.addChild(etiqueta)
    }
} attachments: {
    Attachment(id: "etiqueta-tierra") {
        VStack {
            Text("Tierra")
                .font(.title2.bold())
            Text("149,6 M km del Sol")
                .font(.caption)
        }
        .padding(8)
        .background(.ultraThinMaterial)
        .cornerRadius(8)
    }
}

Ornaments: controles flotantes

Los ornaments son paneles SwiftUI que flotan alrededor de las ventanas de la app, sin ocupar espacio dentro de la ventana:

struct ContentView: View {
    var body: some View {
        Lista3D()
            .ornament(attachmentAnchor: .scene(.bottom)) {
                HStack {
                    Button("Añadir") { }
                    Button("Ordenar") { }
                }
                .padding()
                .glassBackgroundEffect()
            }
    }
}

Resumen

visionOS extiende SwiftUI con tres conceptos clave: RealityView para integrar objetos 3D de RealityKit en la jerarquía de vistas; ImmersiveSpace para experiencias que van más allá de una ventana flotante; y los attachments y ornaments para mezclar vistas SwiftUI con el espacio 3D. La base de conocimiento es SwiftUI y RealityKit, frameworks que ya existían; visionOS añade la capa espacial sin reinventar la rueda.

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP