visionOS: An Introduction

Dec 16 2025 · Swift 6, visionOS 26, Xcode 26

Lesson 02: Volumes & RealityView

Demo

Episode complete

Play next episode

Next
Transcript

You can start with the app in the Starter folder or continue the build from lesson one.

You can find Resources.txt file right inside the Starter folder. Copy the preview code and paste it at the end of CourseView.swift to create the SwiftUI preview for this view.

In the Project Navigator, tap the disclosure indicator next to the Packages to open the folder. Open RealityKitContent, Sources, RealityKitContent, and RealityKitContent.rkassets similarly.

Select Scene.usda, and it’ll display on the center editor pane. If you don’t see a 3D view, right-click the file and choose Open As > Reality Composer Pro Document. Notice that it’s a standard SceneKit file. Use your mouse or trackpad to click and rotate the 3D view. You’ll see that you can have multiple Scene files in the package later.

Next, select Package.realitycomposerpro from the Sources folder. You’ll see the same scene because there’s only one at this point. Tap the Open in Reality Composer Pro button in the top right corner of the view.

In Reality Composer Pro, you’ll see Scene.usda in the Project Browser at the bottom and the same scene in the Navigator on the left. Note the Root element, which contains a Sphere and a _GridMaterial that’s applied to the sphere to make it grey with lines like a globe.

Select the Sphere in the Navigator on the left, and delete it with the Delete key. Also, delete the _GridMaterial. Click the + at the top right or choose Show Content Library from the View menu.

You could use the USDz files here, but they don’t have animations built-in. Head over to Apple’s AR Quick Look gallery. The link is in Resources.txt file in the Starter folder.

Locate the Toy_Biplane and download it. Select the model from your Downloads and drag it into the center screen. Alternatively, you could use the import assets into project button.

With the toy plane selected, look at the Inspector on the right. In the Transform, set the plane’s position to 0,0,0, and the model will move to the center. Notice that the toy plane is a USDz file: it’s made up of multiple geometries and materials.

Note: You can also download other free USDz models online from Apple and sites like Sketchfab. On Sketchfab, you need to register for a free account. Many models are free and also have built-in animation. If you want to use a downloaded model file, drop it into the Project Browser.

Save, Cmd S, the changes to the Package.

Switch back to Xcode and look at the Package in the Project Navigator. Notice that the toy_biplane_idle.usdz file is included.

Right-click the Scene.usda file and choose Open As > Source Code. You’ll now see the XML in the USDa file since it’s ASCII. Notice the Root entity is at the top, followed by the Sphere and _GridMaterial, unless you deleted them from the Project. The toy_biplane_idle, whose filename is toy_biplane_idle.usdz, is below. That’s how you’ll refer to it. For some usdz files, the name and filename could be different.

Add a new SwiftUI file, ’ VolumeView, in the Views` folder.

In the Volume View, you must import Reality Kit and RealityKitContent to support the 3D content.

import RealityKit
import RealityKitContent

Replace the Hello World Text View with a RealityView. Name the make closure argument content. Next, add and unwrap a constant scene and load it async. Notice that you refer to the named Scene in the realityKitContentBundle.

RealityView { content in
  if let scene = try? await Entity(named: "Scene", in: realityKitContentBundle) {
    content.add(scene)
  }
}

Run the Canvas preview, and you’ll see the toy plane. If you want to get a quick view in the Simulator, go to the Vision101App file and change ContentListView() to VolumeView() and Build & Run. Remember to change it back after you’re done.

Note: Sometimes Xcode Canvas previews can be slow, become overwhelmed, or display errors. Run the app in the Simulator.

Select the ContentListView in the Project Navigator and locate the Volume tab. Embed Text("Volume Tab") and modifiers in a NavigationSplitView. Leave .tabItem outside as a view modifier to the new NavigationSplitView. You’ll need to add a detail: closure to fix the warning. Inside here, call an instance of VolumeView().

NavigationSplitView {
  Text("Volume Tab")
    .font(.largeTitle)
    .foregroundColor(.orange)
} detail: {
  VolumeView()
}
.tabItem {
  Image(systemName: "cube")
  Text("Volume")
}

Another Pro Tip: If you want to find a curly braces pair, double click the top brace. Xcode will select all of the code to the matching closing brace. [TODO FPE: I added a period here because it seemed like a run-on.]

Go back to the VolumeView and start the available animations. Inside the closure where you load the scene entity, add a RealityView property, availableAnimations, and playAnimation. This will play any animations that are in the Scene’s files.

scene.availableAnimations.forEach { animation in
  scene.playAnimation(animation.repeat(), transitionDuration: 3, startsPaused: false)
}

Build and run again to enjoy the work you’ve done.

Wouldn’t it be nice to have a button to start the animations? Time to add the option!

In the VolumeView, add a State variable runAnimation and set it to false:

@State var runAnimation = false

Embed the RealityView in a VStack and add a Button to toggle the runAnimation bool.

VStack {
  Button("Start") {
    runAnimation.toggle()
  }
  //..
}

Add an update: closure to the RealityView.

} update: { content in
    if let scene = content.entities.first {

    }
}

Move availableAnimations and playAnimation into the update. Then add a check inside the playAnimation closure to start the animation if the runAnimation bool is true. Add scene.stopAllAnimations() when it’s not true and stop the animations.

Here’s a Pro Tip: If you select a block of code in the editor, you can hold Cmd and Option and press the left square bracket to move the block up. Cmd Opt ] will move the block down.

if runAnimation {
  scene.availableAnimations.forEach { animation in
    scene.playAnimation(animation.repeat(),
    transitionDuration: 3, startsPaused: false)
  }
} else {
  scene.stopAllAnimations()
}

Build and run again. Check out the button.

See forum comments
Cinema mode Download course materials from Github
Previous: Instruction Next: Conclusion