Spatial SwiftUI: Volume Toolbars
SwiftUI Toolbars are presented as Ornaments at the bottom of the volume.
Overview
We can add toolbars to our volumes. The process is similar to adding a toolbar to a window, but with some design considerations to keep in mind.
Learn the basics of toolbars: Spatial SwiftUI: Window Toolbars
As of visionOS 2, the only placement that works for toolbars is bottomOrnament. This placement can work well for small volumes. It can also work well when the content of the volume is located around the bottom. An example is a virtual table top or game board. If the design requires a larger volume with content spread out, then this may not be ideal. Remember, toolbars are essentially just ornaments. If needed, we can create custom ornaments and take much more control of the placement of our controls.
We need a toolbar modifier on our view
.toolbar {
...
}Then we create a ToolbarItemGroup with a placement value of .bottomOrnament.
.toolbar {
ToolbarItemGroup(placement: .bottomOrnament) {
// toolbar content
}
}In this example, I’ve added the buttons for an indirect transform mode. We can use a simple drag gesture to move, rotate, or scale our entity. You can learn how this was built here.
Video Demo
Full Example Code
struct Example047: View {
@State private var transformMode: IndirectTransformMode = .none
@State var earth: Entity = Entity()
@State var earthTransform: Transform = Transform()
var body: some View {
RealityView { content in
if let scene = try? await Entity(named: "RKBasicsLoading", in: realityKitContentBundle) {
content.add(scene)
scene.position.y = -0.4
if let earth = scene.findEntity(named: "Earth") {
self.earth = earth
self.earthTransform = earth.transform
}
if let moon = scene.findEntity(named: "Moon") {
moon.removeFromParent() // We don't need the moon for this example
}
}
}
.modifier(IndirectTransformGesture(mode: $transformMode))
// Add the toolbar modifier
.toolbar {
// Create a ToolbarItemGroup with a placement
// .bottomOrnament seems to be the only placement that works
ToolbarItemGroup(placement: .bottomOrnament) {
Button {
earth.move(to: earthTransform, relativeTo: earth.parent!, duration: 0.3)
} label: {
Image(systemName: "arrow.clockwise")
}
Spacer()
Divider()
Spacer()
Button {
transformMode = .none
} label: {
Image(systemName: "nosign")
}
.background(transformMode == .none ? .stepRed : Color.clear)
.clipShape(.capsule)
Button {
transformMode = .move
} label: {
Image(systemName: "move.3d")
}
.background(transformMode == .move ? .stepRed : Color.clear)
.clipShape(.capsule)
Button {
transformMode = .rotate
} label: {
Image(systemName: "rotate.3d")
}
.background(transformMode == .rotate ? .stepRed : Color.clear)
.clipShape(.capsule)
Button {
transformMode = .scale
} label: {
Image(systemName: "scale.3d")
}
.background(transformMode == .scale ? .stepRed : Color.clear)
.clipShape(.capsule)
}
}
}
}Support our work so we can continue to bring you new examples and articles.
Download the Xcode project with this and many more examples from Step Into Vision.
Some examples are provided as standalone Xcode projects. You can find those here.

Follow Step Into Vision