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.

Questions or feedback?