-
Melhor juntos: SwiftUI e RealityKit
Descubra como integrar perfeitamente o SwiftUI e o RealityKit no visionOS 26. Vamos explorar as melhorias do Model3D, incluindo suporte a animações e compatibilidade com o ConfigurationCatalog, além de demonstrar transições suaves no RealityView. Você aprenderá a usar as animações do SwiftUI para controlar mudanças nos componentes do RealityKit, implementar manipulação interativa, usar os novos componentes do SwiftUI para criar interações mais avançadas e monitorar as alterações do RealityKit com base no seu código do SwiftUI. Também abordaremos o uso da conversão unificada de coordenadas para realizar transformações entre frameworks.
Capítulos
- 0:00 - Introdução
- 1:24 - Aprimoramentos do Model3D
- 6:13 - Transição para o RealityView
- 11:52 - Manipulação de objetos
- 15:35 - Componentes do SwiftUI
- 19:08 - Fluxo de informações
- 24:56 - API Unified Coordinate Conversion
- 27:01 - Animação
- 29:41 - Próximas etapas
Recursos
- Rendering hover effects in Metal immersive apps
- Canyon Crosser: Building a volumetric hike-planning app
Vídeos relacionados
WWDC24
WWDC23
WWDC21
WWDC20
-
Buscar neste vídeo...
-
-
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 - Introdução
Saiba mais sobre os aprimoramentos do SwiftUI do RealityKit que permitem a integração perfeita da interface do usuário tradicional e do conteúdo 3D interativo. As principais atualizações incluem aprimoramentos no Model3D e no RealityView, a introdução da API Object Manipulation, novos tipos de componente, fluxo de dados bidirecional entre o SwiftUI e o RealityKit, conversão de espaço de coordenadas facilitada e animações orientadas por SwiftUI para componentes do RealityKit.
- 1:24 - Aprimoramentos do Model3D
No visionOS 26, o Model3D permite exibir modelos 3D com animações e carregar a partir de um "ConfigurationCatalog". Agora você pode criar experiências 3D interativas com apenas algumas linhas de código. Use o novo tipo "Model3DAsset" para carregar e controlar animações, e o "ConfigurationCatalog" permite alternar entre diferentes representações de uma entidade, como roupas ou tipos de corpo. Para demonstrar esses recursos, temos o exemplo de um robô chamado Sparky, que você pode animar, vestir com diferentes roupas e controlar usando visualizações e seletores SwiftUI, preparando-o para interagir com outros robôs em uma estufa virtual.
- 6:13 - Transição para o RealityView
Para adicionar um emissor de partículas a um modelo 3D em tempo de execução, você deve alternar do uso de "Model3D" para "RealityView" no RealityKit porque o Model3D não suporta a adição de componentes. O RealityView é carregado com a entidade modelo, mas isso causa problemas no layout, pois o padrão ocupa todo o espaço disponível. O modificador "realityViewLayoutBehavior" é aplicado com "fixedSize" para resolver esse problema, ajustando o RealityView ao modelo. Há três opções de comportamento diferentes de "realityViewLayoutBehavior". "flexible", "centered" e "fixedSize", cada um afetando o ponto de origem do RealityView sem reposicionar as entidades. Use o Reality Composer Pro para projetar emissores de partículas que podem ser configurados no código. Emissores de partículas são adicionados a entidades como componentes. O RealityKit tem como base o paradigma "Entity Component System". O exemplo inclui emissores de partículas para criar efeitos de faísca, envolvendo entidades invisíveis como contêineres para as faíscas. O RealityView é ótimo para criação detalhada e controle refinado sobre o comportamento do conteúdo 3D, enquanto o Model3D é adequado para exibir assets 3D independentes. Você pode fazer a transição suave entre os dois conforme necessário.
- 11:52 - Manipulação de objetos
A nova API Object Manipulation no visionOS 26 permite interagir com objetos virtuais em apps que usam SwiftUI e RealityKit. As pessoas podem mover, girar e escalar objetos com as mãos, e até mesmo passar objetos entre as mãos. Para visualizações do SwiftUI, você pode aplicar o modificador 'manipulable', permitindo a personalização das operações suportadas e a inércia do objeto. No RealityKit, adicione o "ManipulationComponent" às entidades por meio da função estática "configureEntity", que também adiciona componentes de colisão, destino de entrada e efeito de foco. A função tem vários parâmetros para personalização de comportamento. Assine "ManipulationEvents" acionados durante interações, como inícios, paradas, atualizações, lançamentos e transferências, para atualizar o estado do app e personalizar a experiência, incluindo a substituição de sons padrão por recursos de áudio personalizados.
- 15:35 - Componentes do SwiftUI
Os novos componentes do SwiftUI RealityKit melhoram a interação e a conexão do usuário dentro das cenas do RealityKit. Esses componentes permitem integrar perfeitamente visualizações do SwiftUI em ambientes 3D. Os principais recursos incluem o "ViewAttachmentComponent", que permite adicionar visualizações do SwiftUI diretamente a entidades; o "GestureComponent", tornando as entidades responsivas ao toque e movimentos; e o "PresentationComponent", que permite apresentar visualizações do SwiftUI, como popovers, dentro da cena. O parâmetro de configuração no "PresentationComponent" define o tipo de apresentação para exibição. Essas melhorias simplificam o processo de desenvolvimento, permitindo criar experiências mais dinâmicas e envolventes. No exemplo, um robô chamado Bolts pode ter um sinal de nome que liga e desliga com um movimento de toque, e as pessoas podem escolher a roupa dele em um menu popover, tudo dentro do ambiente imersivo do RealityKit.
- 19:08 - Fluxo de informações
No visionOS 26, as entidades agora são observáveis, o que permite que entidades, como o robô Bolts, notifiquem outro código quando suas propriedades mudarem. Isso é particularmente útil para rastrear os movimentos do Bolts enquanto rega as plantas na estufa. O exemplo usa o SwiftUI para implementar um minimapa que exibe a posição do Bolts em tempo real. Acessando a propriedade de posição observável do Bolts, o SwiftUI atualiza automaticamente o minimapa sempre que Bolts se move. Esse fluxo de dados bidirecional entre o SwiftUI e o RealityKit, habilitado por entidades observáveis, é uma nova ferramenta significativa. Essa nova funcionalidade observável também introduz o potencial de loops infinitos, caso não seja gerenciada com cuidado. Para evitar esses loops, atente-se a onde você modifica o estado observado. Não faça alterações no estado observado dentro do fechamento "update" do "RealityView", porque isso pode desencadear um ciclo de atualização recursivo. Em vez disso, faça modificações em locais específicos, como sistemas personalizados, fechamentos por movimento ou o fechamento "make", que estão fora do escopo da avaliação do corpo de visualização da SwiftUI. Dividindo visualizações maiores em visualizações menores e independentes e tendo cautela quanto a onde o estado é modificado, você pode garantir um fluxo de dados suave e eficiente entre o SwiftUI e o RealityKit, evitando loops infinitos e melhorando o desempenho geral do app.
- 24:56 - API Unified Coordinate Conversion
A nova API de conversão de coordenadas unificadas preenche a lacuna entre o RealityKit e o SwiftUI. Implementando o protocolo "CoordinateSpace3D", você pode converter valores perfeitamente entre os dois frameworks. Isso permite calcular a distância absoluta entre Bolts, uma entidade no RealityKit, e Sparky, um Model3D em SwiftUI, à medida que se aproximam, gerando faíscas dinamicamente com base em sua proximidade.
- 27:01 - Animação
No visionOS 26, é possível aproveitar as APIs de animação do SwiftUI para animar implicitamente as mudanças de componentes do RealityKit. Isso permite movimentos suaves e dinâmicos de entidades simplesmente definindo componentes compatíveis dentro de um bloco de animação. Existem dois métodos para conseguir isso: usando "content.animate()" dentro de um RealityView ou chamando "Entity.animate()" diretamente. Essa integração permite comportamentos personalizados de liberação ao manipular entidades, tornando as interações com objetos 3D mais envolventes e divertidas. Vários componentes do RealityKit, incluindo Transformar, Áudio, Modelo e Luz, oferecem suporte a essas animações implícitas.
- 29:41 - Próximas etapas
Use essa nova conexão entre SwiftUI e RealityKit para criar apps espaciais inovadores, integrando componentes SwiftUI com cenas do RealityKit, permitindo interações dinâmicas entre os mundos virtual e físico e inspirando novas possibilidades no desenvolvimento de apps.