-
Mieux ensemble : SwiftUI et RealityKit
Découvrez comment combiner harmonieusement SwiftUI et RealityKit dans visionOS 26. Nous explorerons les améliorations apportées à Model3D, y compris la prise en charge de l'animation et de ConfigurationCatalog, et démontrerons des transitions en douceur vers RealityView. Vous apprendrez à tirer parti des animations SwiftUI pour piloter les modifications de composants RealityKit, à mettre en œuvre des manipulations interactives, à utiliser les nouveaux composants SwiftUI pour des interactions plus riches et à observer les modifications apportées à RealityKit depuis votre code SwiftUI. Nous verrons également comment utiliser la conversion unifiée des coordonnées pour effectuer des transformations de coordonnées entre différents frameworks.
Chapitres
- 0:00 - Introduction
- 1:24 - Améliorations de Model3D
- 6:13 - Transition vers RealityView
- 11:52 - Manipulation d’objets
- 15:35 - Composants SwiftUI
- 19:08 - Flux d’informations
- 24:56 - Conversion de coordonnées unifiées
- 27:01 - Animation
- 29:41 - Étapes suivantes
Ressources
- Rendering hover effects in Metal immersive apps
- Canyon Crosser: Building a volumetric hike-planning app
Vidéos connexes
WWDC24
WWDC23
WWDC21
WWDC20
-
Rechercher dans cette vidéo…
-
-
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 - Introduction
Découvrez les améliorations de RealityKit et SwiftUI. Elles permettent une intégration transparente de l’interface utilisateur traditionnelle et du contenu 3D interactif. Parmi les principales mises à jour figurent les améliorations apportées à Model3D et RealityView, l’introduction de l’API Object Manipulation, de nouveaux types de composants, le flux de données bidirectionnel entre SwiftUI et RealityKit, une conversion plus facile de l’espace de coordonnées et des animations pilotées par SwiftUI pour les composants RealityKit.
- 1:24 - Améliorations de Model3D
Dans visionOS 26, Model3D permet d’afficher des modèles 3D avec des animations et de les charger à partir d’un « ConfigurationCatalog ». Vous pouvez désormais créer des expériences 3D interactives avec seulement quelques lignes de code. Utilisez le nouveau type « Model3DAsset » pour charger et contrôler les animations, et « ConfigurationCatalog » pour basculer entre les différentes représentations d’une entité, telles que des tenues ou des types de corps. L’exemple d’un robot nommé Sparky, que vous pouvez animer, habiller de différentes tenues et contrôler à l’aide des vues et des sélecteurs SwiftUI, afin qu’il soit prêt à interagir avec d’autres robots dans une serre virtuelle, présente ces fonctionnalités.
- 6:13 - Transition vers RealityView
Pour ajouter un émetteur de particules à un modèle 3D au moment de l’exécution, vous devez passer de l’utilisation de « Model3D » à « RealityView » dans RealityKit, car Model3D ne prend pas en charge l’ajout de composants. RealityView est chargé avec l’entité modèle, mais cela pose des problèmes de mise en forme, car il occupe par défaut tout l’espace disponible. Le modificateur « realityViewLayoutBehavior » est appliqué avec « fixedSize » pour résoudre ce problème, afin que RealityView s’adapte aux limites initiales du modèle. Il existe trois options « realityViewLayoutBehavior » différentes : « flexible », « centered » et « fixedSize », chacune affectant le point d’origine de RealityView sans repositionner les entités. Utilisez Reality Composer Pro pour concevoir des émetteurs de particules configurables dans le code. Les émetteurs de particules sont ajoutés aux entités en tant que composants. RealityKit est basé sur le paradigme « Entity Component System ». L’exemple ajoute des émetteurs de particules pour créer des effets d’étincelles, en utilisant des entités invisibles comme conteneurs pour les étincelles. RealityView est idéal pour la création détaillée et le contrôle fin du comportement du contenu 3D, tandis que Model3D convient à l’affichage de ressources 3D autonomes. Vous pouvez passer en douceur de l’un à l’autre, selon vos besoins.
- 11:52 - Manipulation d’objets
La nouvelle API Object Manipulation de visionOS 26 permet d’interagir avec des objets virtuels dans des apps qui utilisent SwiftUI et RealityKit. Il est possible de déplacer, faire pivoter et mettre à l’échelle des objets avec les mains, et même de passer des objets d’une main à l’autre. Pour les vues SwiftUI, vous pouvez appliquer le modificateur « manipulable », ce qui permet de personnaliser les opérations prises en charge et l’inertie des objets. Dans RealityKit, ajoutez le « ManipulationComponent » aux entités via la fonction statique « configureEntity », qui ajoute également des composants de type collision, cible d’entrée et effet de survol. La fonction a plusieurs paramètres pour la personnalisation du comportement. Abonnez-vous à des « ManipulationEvents » déclenchés lors d’interactions, telles que les démarrages, les arrêts, les mises à jour, les versions et les transferts, afin de mettre à jour l’état de l’app et de personnaliser l’expérience, notamment en remplaçant les sons par défaut par des ressources audio personnalisées.
- 15:35 - Composants SwiftUI
Les nouveaux composants SwiftUI et RealityKit améliorent les interactions et la connexion de l’utilisateur au sein des scènes RealityKit. Ces composants vous permettent d’intégrer en toute transparence les vues SwiftUI dans des environnements 3D. Parmi les principales fonctionnalités, citons le « ViewAttachmentComponent », pour ajouter des vues SwiftUI directement aux entités ; le « GestureComponent », pour rendre les entités réactives au toucher et aux gestes ; et le « PresentationComponent », pour présenter des vues SwiftUI, telles que des popovers, au sein de la scène. Le paramètre de configuration de « PresentationComponent » correspond au type de présentation à afficher. Ces améliorations simplifient le processus de développement, vous permettant de créer des expériences plus dynamiques et plus captivantes. Dans l’exemple, un robot nommé Bolts peut avoir un nom qui s’allume et s’éteint d’un simple geste, et il est possible de choisir la tenue de Bolts dans un menu contextuel, le tout dans l’environnement immersif de RealityKit.
- 19:08 - Flux d’informations
Dans visionOS 26, les entités sont désormais observables, ce qui permet aux entités, telles que le robot Bolts, de notifier d’autres codes lorsque leurs propriétés changent. Cela est particulièrement utile pour suivre les mouvements de Bolts lorsqu’il arrose les plantes dans la serre. L’exemple utilise SwiftUI pour implémenter une mini-carte qui affiche la position de Bolts en temps réel. En accédant à la propriété de position observable de Bolts, SwiftUI met automatiquement à jour la mini-carte chaque fois que Bolts se déplace. Ce flux de données bidirectionnel entre SwiftUI et RealityKit, rendu possible par des entités observables, constitue un nouvel outil important. Cette nouvelle fonctionnalité d’observation crée aussi des boucles infinies si elle n’est pas gérée avec soin. Pour éviter ces boucles, faites attention à l’endroit où vous modifiez l’état observé. Ne modifiez pas l’état observé lors de la fermeture « update » de « RealityView », au risque de déclencher un cycle de mise à jour récursif. Au lieu de cela, effectuez des modifications dans des endroits spécifiques, tels que les systèmes personnalisés, les fermetures gestuelles ou la fermeture « make », qui ne sont pas couverts par l’évaluation du corps de la vue de SwiftUI. En décomposant les grandes vues en éléments plus petits et autonomes, et en faisant attention à l’endroit où l’état est modifié, vous pouvez garantir un flux de données fluide et efficace entre SwiftUI et RealityKit, en évitant les boucles infinies et en améliorant les performances globales de l’app.
- 24:56 - Conversion de coordonnées unifiées
La nouvelle API Unified Coordinate Conversion comble le fossé entre RealityKit et SwiftUI. En implémentant le protocole « CoordinateSpace3D », vous pouvez convertir des valeurs de manière fluide entre les deux frameworks. Ainsi, vous calculez la distance absolue entre Bolts, une entité dans RealityKit, et Sparky, un Model3D dans SwiftUI, à mesure qu’ils se rapprochent les uns des autres, générant dynamiquement des étincelles en fonction de leur proximité.
- 27:01 - Animation
Dans visionOS 26, vous pouvez désormais exploiter les API d’animation de SwiftUI pour animer implicitement les modifications apportées aux composants RealityKit. Cela permet d’obtenir des mouvements fluides et rebondissants des entités par simple définition des composants pris en charge dans un bloc d’animation. Pour y parvenir, il existe deux méthodes : en utilisant « content.animate() » dans un RealityView ou en appelant directement « Entity.animate() ». Cette intégration permet de personnaliser les comportements de relâchement lors de la manipulation des entités, ce qui rend les interactions avec les objets 3D plus attrayantes et plus amusantes. Différents composants RealityKit, dont Transform, Audio, Model et Light, prennent en charge ces animations implicites.
- 29:41 - Étapes suivantes
Utilisez cette nouvelle connexion entre SwiftUI et RealityKit pour créer des apps spatiales innovantes en intégrant des composants SwiftUI aux scènes RealityKit, en permettant des interactions dynamiques entre les mondes virtuels et physiques, et en faisant émerger de nouvelles potentialités dans le développement d’apps.