-
Mejor juntos: SwiftUI y RealityKit
Descubre cómo combinar sin problemas SwiftUI y RealityKit en visionOS 26. Exploraremos las mejoras a Model3D, incluida la animación y la compatibilidad con ConfigurationCatalog, y demostraremos transiciones fluidas a RealityView. Aprenderás a aprovechar las animaciones de SwiftUI para impulsar cambios en los componentes de RealityKit, implementar manipulación interactiva, usar nuevos componentes de SwiftUI para interacciones más enriquecidas y observar los cambios de RealityKit desde tu código de SwiftUI. También hablaremos sobre cómo usar la conversión de coordenadas unificada para transformaciones de coordenadas entre estructuras.
Capítulos
- 0:00 - Introducción
- 1:24 - Mejoras a Model3D
- 6:13 - Transición de RealityView
- 11:52 - Manipulación de objetos
- 15:35 - Componentes de SwiftUI
- 19:08 - Flujo de información
- 24:56 - Conversión de coordenadas unificada
- 27:01 - Animación
- 29:41 - Próximos pasos
Recursos
- Rendering hover effects in Metal immersive apps
- Canyon Crosser: Building a volumetric hike-planning app
Videos relacionados
WWDC24
WWDC23
WWDC21
WWDC20
-
Buscar este video…
-
-
1:42 - Sparky in Model3D
struct ContentView: View { var body: some View { Model3D(named: "sparky") } } -
1:52 - Sparky in Model3D with a name sign
struct ContentView: View { var body: some View { HStack { NameSign() Model3D(named: "sparky") } } } -
struct RobotView: View { @State private var asset: Model3DAsset? var body: some View { if asset == nil { ProgressView().task { asset = try? await Model3DAsset(named: "sparky") } } } } -
struct RobotView: View { @State private var asset: Model3DAsset? var body: some View { if asset == nil { ProgressView().task { asset = try? await Model3DAsset(named: "sparky") } } else if let asset { VStack { Model3D(asset: asset) AnimationPicker(asset: asset) } } } } -
struct RobotView: View { @State private var asset: Model3DAsset? var body: some View { if asset == nil { ProgressView().task { asset = try? await Model3DAsset(named: "sparky") } } else if let asset { VStack { Model3D(asset: asset) AnimationPicker(asset: asset) if let animationController = asset.animationPlaybackController { RobotAnimationControls(playbackController: animationController) } } } } } -
struct RobotAnimationControls: View { @Bindable var controller: AnimationPlaybackController var body: some View { HStack { Button(controller.isPlaying ? "Pause" : "Play") { if controller.isPlaying { controller.pause() } else { controller.resume() } } Slider( value: $controller.time, in: 0...controller.duration ).id(controller) } } } -
5:41 - Load a Model3D using a ConfigurationCatalog
struct ConfigCatalogExample: View { @State private var configCatalog: Entity.ConfigurationCatalog? @State private var configurations = [String: String]() @State private var showConfig = false var body: some View { if let configCatalog { Model3D(from: configCatalog, configurations: configurations) .popover(isPresented: $showConfig, arrowEdge: .leading) { ConfigPicker( name: "outfits", configCatalog: configCatalog, chosenConfig: $configurations["outfits"]) } } else { ProgressView() .task { await loadConfigurationCatalog() } } } } -
6:51 - Switching from Model3D to RealityView
struct RobotView: View { let url: URL = Bundle.main.url(forResource: "sparky", withExtension: "reality")! var body: some View { HStack { NameSign() RealityView { content in if let sparky = try? await Entity(contentsOf: url) { content.add(sparky) } } } } } -
7:25 - Switching from Model3D to RealityView with layout behavior
struct RobotView: View { let url: URL = Bundle.main.url(forResource: "sparky", withExtension: "reality")! var body: some View { HStack { NameSign() RealityView { content in if let sparky = try? await Entity(contentsOf: url) { content.add(sparky) } } .realityViewLayoutBehavior(.fixedSize) } } } -
8:48 - Switching from Model3D to RealityView with layout behavior and RealityKit animation
struct RobotView: View { let url: URL = Bundle.main.url(forResource: "sparky", withExtension: "reality")! var body: some View { HStack { NameSign() RealityView { content in if let sparky = try? await Entity(contentsOf: url) { content.add(sparky) sparky.playAnimation(getAnimation()) } } .realityViewLayoutBehavior(.fixedSize) } } } -
10:34 - Add 2 particle emitters; one to each side of the robot's head
func setupSparks(robotHead: Entity) { let leftSparks = Entity() let rightSparks = Entity() robotHead.addChild(leftSparks) robotHead.addChild(rightSparks) rightSparks.components.set(sparksComponent()) leftSparks.components.set(sparksComponent()) leftSparks.transform.rotation = simd_quatf(Rotation3D( angle: .degrees(180), axis: .y)) leftSparks.transform.translation = leftEarOffset() rightSparks.transform.translation = rightEarOffset() } // Create and configure the ParticleEmitterComponent func sparksComponent() -> ParticleEmitterComponent { ... } -
12:30 - Apply the manipulable view modifier
struct RobotView: View { let url: URL var body: some View { HStack { NameSign() Model3D(url: url) .manipulable() } } } -
12:33 - Allow translate, 1- and 2-handed rotation, but not scaling
struct RobotView: View { let url: URL var body: some View { HStack { NameSign() Model3D(url: url) .manipulable( operations: [.translation, .primaryRotation, .secondaryRotation] ) } } } -
12:41 - The model feels heavy with high inertia
struct RobotView: View { let url: URL var body: some View { HStack { NameSign() Model3D(url: url) .manipulable(inertia: .high) } } } -
13:18 - Add a ManipulationComponent to an entity
RealityView { content in let sparky = await loadSparky() content.add(sparky) ManipulationComponent.configureEntity(sparky) } -
RealityView { content in let sparky = await loadSparky() content.add(sparky) ManipulationComponent.configureEntity( sparky, hoverEffect: .spotlight(.init(color: .purple)), allowedInputTypes: .all, collisionShapes: myCollisionShapes() ) } -
14:08 - Manipulation interaction events
public enum ManipulationEvents { /// When an interaction is about to begin on a ManipulationComponent's entity public struct WillBegin: Event { } /// When an entity's transform was updated during a ManipulationComponent public struct DidUpdateTransform: Event { } /// When an entity was released public struct WillRelease: Event { } /// When the object has reached its destination and will no longer be updated public struct WillEnd: Event { } /// When the object is directly handed off from one hand to another public struct DidHandOff: Event { } } -
14:32 - Replace the standard sounds with custom ones
RealityView { content in let sparky = await loadSparky() content.add(sparky) var manipulation = ManipulationComponent() manipulation.audioConfiguration = .none sparky.components.set(manipulation) didHandOff = content.subscribe(to: ManipulationEvents.DidHandOff.self) { event in sparky.playAudio(handoffSound) } } -
16:19 - Builder based attachments
struct RealityViewAttachments: View { var body: some View { RealityView { content, attachments in let bolts = await loadAndSetupBolts() if let nameSign = attachments.entity( for: "name-sign" ) { content.add(nameSign) place(nameSign, above: bolts) } content.add(bolts) } attachments: { Attachment(id: "name-sign") { NameSign("Bolts") } } .realityViewLayoutBehavior(.centered) } } -
16:37 - Attachments created with ViewAttachmentComponent
struct AttachmentComponentAttachments: View { var body: some View { RealityView { content in let bolts = await loadAndSetupBolts() let attachment = ViewAttachmentComponent( rootView: NameSign("Bolts")) let nameSign = Entity(components: attachment) place(nameSign, above: bolts) content.add(bolts) content.add(nameSign) } .realityViewLayoutBehavior(.centered) } } -
17:04 - Targeted to entity gesture API
struct AttachmentComponentAttachments: View { @State private var bolts = Entity() @State private var nameSign = Entity() var body: some View { RealityView { ... } .realityViewLayoutBehavior(.centered) .gesture( TapGesture() .targetedToEntity(bolts) .onEnded { value in nameSign.isEnabled.toggle() } ) } } -
17:10 - Gestures with GestureComponent
struct AttachmentComponentAttachments: View { var body: some View { RealityView { content in let bolts = await loadAndSetupBolts() let attachment = ViewAttachmentComponent( rootView: NameSign("Bolts")) let nameSign = Entity(components: attachment) place(nameSign, above: bolts) bolts.components.set(GestureComponent( TapGesture().onEnded { nameSign.isEnabled.toggle() } )) content.add(bolts) content.add(nameSign) } .realityViewLayoutBehavior(.centered) } }
-
-
- 0:00 - Introducción
Obtén información sobre las mejoras de RealityKit SwiftUI que permiten una integración perfecta de la interfaz de usuario tradicional y el contenido 3D interactivo. Las actualizaciones clave incluyen mejoras en Model3D y RealityView, la presentación de la API Object Manipulation, nuevos tipos de componentes, flujo de datos bidireccional entre SwiftUI y RealityKit, la conversión de espacio de coordenadas más sencilla, y animaciones impulsadas por SwiftUI para componentes RealityKit.
- 1:24 - Mejoras a Model3D
En visionOS 26, Model3D permite la visualización de modelos 3D con animaciones y la carga desde un 'ConfigurationCatalog'. Ahora puedes crear experiencias 3D interactivas con solo unas pocas líneas de código. Utiliza el nuevo tipo 'Model3DAsset' para cargar y controlar animaciones, y 'ConfigurationCatalog' para cambiar entre diferentes representaciones de una entidad, como atuendos o tipos de cuerpo. El ejemplo de un robot llamado Sparky, al que puedes animar, vestir con distintos atuendos y controlar mediante vistas y selectores SwiftUI, preparándolo para interactuar con otros robots en un invernadero virtual, demuestra estas características.
- 6:13 - Transición de RealityView
Para agregar un emisor de partículas a un modelo 3D en tiempo de ejecución, debes cambiar de 'Model3D' a 'RealityView' en RealityKit ya que Model3D no admite la adición de componentes. RealityView se carga con la entidad del modelo, pero esto genera problemas de diseño ya que, de manera predeterminada, ocupa todo el espacio disponible. El modificador 'realityViewLayoutBehavior' se aplica con 'fixedSize' para resolver esto, lo que hace que RealityView ajuste perfectamente los límites iniciales del modelo. Hay tres opciones diferentes de 'realityViewLayoutBehavior': 'flexible', 'centered' y 'fixedSize', cada uno de los cuales afecta el punto de origen de RealityView sin reposicionar las entidades. Utiliza Reality Composer Pro para diseñar emisores de partículas que se puedan configurar en el código. Los emisores de partículas se agregan a las entidades como componentes. RealityKit se basa en el paradigma "Entity Component System". El ejemplo agrega emisores de partículas para crear efectos de chispas, involucrando entidades invisibles como contenedores para las chispas. RealityView es ideal para la creación detallada y el control preciso del comportamiento del contenido 3D, mientras que Model3D es adecuado para mostrar activos 3D autónomos. Puedes realizar una transición suave entre ambos según sea necesario.
- 11:52 - Manipulación de objetos
La nueva API Object Manipulation en visionOS 26 permite interactuar con objetos virtuales en apps que usan SwiftUI y RealityKit. Los usuarios pueden mover, rotar y escalar objetos con sus manos, e incluso pasar objetos entre ellas. Para las vistas SwiftUI, puedes aplicar el modificador 'manipulable', que permite personalizar las operaciones admitidas y la inercia de los objetos. En RealityKit, agrega 'ManipulationComponent' a las entidades a través de la función estática 'configureEntity', la cual también agrega componentes de colisión, de objetivo de entrada y de efecto de desplazamiento. La función tiene varios parámetros para personalizar el comportamiento. Suscribir a 'ManipulationEvents' activados durante interacciones, como inicios, detenciones, actualizaciones, lanzamientos y transferencias, para actualizar el estado de la app y personalizar la experiencia, incluido el reemplazo de sonidos predeterminados con recursos de audio personalizados.
- 15:35 - Componentes de SwiftUI
Los nuevos componentes SwiftUI RealityKit mejoran la interacción y la conexión del usuario dentro de las escenas RealityKit. Estos componentes te permiten integrar sin problemas las vistas SwiftUI en entornos 3D. Las características clave incluyen 'ViewAttachmentComponent', el cual te permite agregar vistas SwiftUI directamente a las entidades; 'GestureComponent', que hace que las entidades respondan al tacto y a los gestos; y 'PresentationComponent', el cual permite la presentación de vistas SwiftUI, como ventanas emergentes dentro de la escena. El parámetro de configuración en 'PresentationComponent' es el tipo de presentación a mostrar. Estas mejoras simplifican el proceso de desarrollo y te permiten crear experiencias más dinámicas y atractivas. En el ejemplo, un robot llamado Bolts puede tener un cartel con su nombre que se activa y desactiva con un toque y las personas pueden elegir el atuendo de Bolts desde un menú emergente, todo dentro del entorno inmersivo de RealityKit.
- 19:08 - Flujo de información
En visionOS 26, las entidades ahora son observables, lo que permite que entidades como el robot Bolts notifiquen a otro código cuando sus propiedades cambian. Esto es particularmente útil para rastrear los movimientos de Bolts mientras riega las plantas en el invernadero. El ejemplo utiliza SwiftUI para implementar un minimapa que muestra la posición de Bolts en tiempo real. Al acceder a la propiedad de posición observable de Bolts, SwiftUI actualiza automáticamente el minimapa cada vez que Bolts se mueve. Este flujo de datos bidireccional entre SwiftUI y RealityKit, habilitado por entidades observables, es una nueva herramienta importante. Esta nueva funcionalidad observable también introduce la posibilidad de bucles infinitos si no se gestiona con cuidado. Para evitar estos bucles, cuida dónde modificas el estado observado. No realices cambios en el estado observado dentro del cierre de 'actualización' de 'RealityView', porque esto puede desencadenar un ciclo de actualización recursivo. Mejor, realiza modificaciones en lugares específicos, como sistemas personalizados, cierres de gestos o el cierre 'make' que están fuera del alcance de la evaluación del cuerpo de la vista de SwiftUI. Al dividir vistas más grandes en vistas más pequeñas y autónomas y cuidar dónde se modifica el estado, puedes garantizar un flujo de datos eficiente y sin problemas entre SwiftUI y RealityKit. Con esto evitas bucles infinitos y mejoras el rendimiento general de la app.
- 24:56 - Conversión de coordenadas unificada
La nueva API Unified Coordinate Conversion cierra la brecha entre RealityKit y SwiftUI. Al implementar el protocolo 'CoordinateSpace3D', puedes convertir valores sin problemas entre las dos estructuras. Esto permite calcular la distancia absoluta entre Bolts, una entidad en RealityKit, y Sparky, un Model3D en SwiftUI, a medida que se acercan y generan chispas dinámicamente en función de su proximidad.
- 27:01 - Animación
En visionOS 26, ahora puedes aprovechar las API de animación de SwiftUI para animar implícitamente los cambios en los componentes de RealityKit. Esto permite movimientos suaves y dinámicos de las entidades simplemente al configurar los componentes compatibles dentro de un bloque de animación. Hay dos métodos para lograr esto: utilizar 'content.animate()' dentro de una RealityView o llamando a 'Entity.animate()' directamente. Esta integración permite comportamientos de liberación personalizados al manipular entidades, lo que hace que las interacciones con objetos 3D sean más atractivas y divertidas. Varios componentes de RealityKit, incluidos Transform, Audio, Model y Light, admiten estas animaciones implícitas.
- 29:41 - Próximos pasos
Utiliza esta nueva conexión entre SwiftUI y RealityKit para crear aplicaciones espaciales innovadoras al integrar componentes de SwiftUI con escenas de RealityKit, lo cual permite interacciones dinámicas entre los mundos virtuales y físicos e inspira nuevas posibilidades en el desarrollo de apps.