Spatial SwiftUI: GeometryReader3D
Reading Size3D and BoundingBox data from a proxy.
We can use GeometryReader3D just like we use GeometryReader in SwiftUI. It can be useful to adapt our SwiftUI views to the area reported in the proxy. We can also use it to update our RealityKit content to the fit size of a window or volume.
Let’s take a look at using this with a volume. We’ll resize an entity to fill the available space in the volume as the user resizes it.
First we’ll wrap the RealityView in GeometryReader3D.
GeometryReader3D { proxy in
RealityView { content in
content.add(subject)
} update: { content in
}
}We can get to the data we need through the proxy.
- size: a Size3D
- frame: a BoundingBox
When working with Volumes, I normally use frame. The frame is a bounding box that describes is location and size. In our case, we only care about the extents of the frame. We can convert it using the RealityViewContent (content) from our RealityView. Then we can use the extents to scale our entity.
let newFrame = content.convert(proxy.frame(in: .global), from: .global, to: .scene)
subject.scale = [newFrame.extents.x, newFrame.extents.y, newFrame.extents.z]An alternative approach is to use the Size3D value from the proxy.
let newSize = content.convert(proxy.size, from: .global, to: .scene)
subject.scale = .init(newSize)This is looks a little easier than using frame, but there is an issue. For some reason, when I use this with a Volume, the converted Y axis value is always a negative number. I’m not sure if this is a bug or a misunderstanding on my part.
new frame requested SIMD3<Float>(0.7352941, 0.7352941, 0.7352941)
new size requested SIMD3<Float>(0.7352941, -0.7352941, 0.7352941)Notice the -0.7352941 value in the Y position of the size. If we scale out box by this value it will be turned inside out. We could get the absolute value, but I’d prefer to stick with the frame/extents method.
Example Code
struct Example081: View {
// Set up a subject entity
@State var subject: Entity = {
let mat = SimpleMaterial(color: .stepGreen, roughness: 0.2, isMetallic: false)
let box = ModelEntity(
mesh: .generateBox(width: 1, height: 1, depth: 1, cornerRadius: 0.06),
materials: [mat])
return box
}()
var body: some View {
GeometryReader3D { proxy in
RealityView { content in
content.add(subject)
} update: { content in
// Update the subject scale anytime the proxy changes
scaleSubjectWithFrame(content: content, proxy: proxy, subject: subject)
}
}
}
// Example 1: converting Size3D to a new scale for our entity
func scaleSubjectWithSize(content: RealityViewContent, proxy: GeometryProxy3D, subject: Entity) {
let newSize = content.convert(proxy.size, from: .global, to: .scene)
print("new size requested \(newSize)")
subject.scale = .init(newSize)
}
// Example 2: converting the frame extents to a new scale for our entity
func scaleSubjectWithFrame(content: RealityViewContent, proxy: GeometryProxy3D, subject: Entity) {
let newFrame = content.convert(proxy.frame(in: .global), from: .global, to: .scene)
print("new frame requested \(newFrame.extents)")
subject.scale = [newFrame.extents.x, newFrame.extents.y, newFrame.extents.z]
}
}Note: the volume in this example is set up to resize based on content. Learn more about window and volume setup.
WindowGroup(id: "RouterVolume", for: String.self, content: { $route in
let initialSize = Size3D(width: 500, height: 500, depth: 500)
let scaler = 4.0
ExampleRouter(route: $route)
.frame(minWidth: initialSize.width, maxWidth: initialSize.width * scaler,
minHeight: initialSize.height, maxHeight: initialSize.height * scaler)
.frame(minDepth: initialSize.depth, maxDepth: initialSize.depth * scaler)
})
.windowStyle(.volumetric)
.defaultWindowPlacement { _, context in
if let mainWindow = context.windows.first {
return WindowPlacement(.trailing(mainWindow))
}
return WindowPlacement(.none)
}
.defaultSize(width: 1000, height: 1000, depth: 1000)
.windowResizability(.contentSize)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