Lab 078 – Building a glass material box
Using spatialOverlay wrap any SwiftUI view in glass material panes.
In Lab 075 we took a look at debugBorder3D. Apple shared this useful utility function at WWDC. Today I decided to adapt it to render glass material panes around a SwiftUI View.
I started with the four sides they provided in debugBorder3D. This creates the back, front, left, and right sides of a box. This uses spatialOverlay to draw a Spatial Container on top of the parent view, then uses rotation3DLayout to rotate the left and right panes.
spatialOverlay {
ZStack {
// Back
Color.clear
.glassBackgroundEffect(displayMode: backDisplayMode)
.padding(padding)
// Create the sides just the like back and front, but rotate them on y
ZStack {
Color.clear
.glassBackgroundEffect(displayMode: leadingDisplayMode)
.padding(padding)
Spacer()
Color.clear
.glassBackgroundEffect(displayMode: trailingDisplayMode)
.padding(padding)
}
.rotation3DLayout(.degrees(90), axis: .y)
// Front
Color.clear
.glassBackgroundEffect(displayMode: frontDisplayMode)
.padding(padding)
}
}For the top and bottom I added a second spatial overlay to this one. This use the same concept but will rotate these panes on the X axis instead of Y.
.spatialOverlay {
// see above
}
.spatialOverlay {
ZStack {
Color.clear
.glassBackgroundEffect(displayMode: bottomDisplayMode)
.padding(padding)
Spacer()
Color.clear
.glassBackgroundEffect(displayMode: topDisplayMode)
.padding(padding)
}
.rotation3DLayout(.degrees(90), axis: .x)
}Next I wanted to be able show or hide panes. I decided to use Edge3D.Set.
func glassBackgroundBox(padding: CGFloat = 0, _ directions: Edge3D.Set) -> some View {
...
}This will let me do things like this.
.glassBackgroundBox(padding: 12, .top, .bottom)
.glassBackgroundBox(padding: 12, .vertical)
.glassBackgroundBox(padding: 12, .all)I used the displayMode value of glassBackgroundEffect to show and hide each pane based on the current value of directions. For example, the top pane looks at three conditions.
let topDisplayMode: GlassBackgroundDisplayMode = directions.contains(.top) || directions.contains(.all) || directions.contains(.vertical) ? .always : .neverLet’s see it in action.
visionOS Simulator video showing a 3D cube surrounded by six glass panes
Full Lab Code
This still needs some work, but here is the full function.
xtension View {
func glassBackgroundBox(padding: CGFloat = 0, _ directions: Edge3D.Set) -> some View {
// compute the display mode based on the edge set
let topDisplayMode: GlassBackgroundDisplayMode = directions.contains(.top) || directions.contains(.all) || directions.contains(.vertical) ? .always : .never
let bottomDisplayMode: GlassBackgroundDisplayMode = directions.contains(.bottom) || directions.contains(.all) || directions.contains(.vertical) ? .always : .never
let leadingDisplayMode: GlassBackgroundDisplayMode = directions.contains(.leading) || directions.contains(.all) || directions.contains(.horizontal) ? .always : .never
let trailingDisplayMode: GlassBackgroundDisplayMode = directions.contains(.trailing) || directions.contains(.all) || directions.contains(.horizontal) ? .always : .never
let frontDisplayMode: GlassBackgroundDisplayMode = directions.contains(.front) || directions.contains(.all) || directions.contains(.depth) ? .always : .never
let backDisplayMode: GlassBackgroundDisplayMode = directions.contains(.back) || directions.contains(.all) || directions.contains(.depth) ? .always : .never
return spatialOverlay {
ZStack {
// Back
Color.clear
.glassBackgroundEffect(displayMode: backDisplayMode)
.padding(padding)
// Create the sides just the like back and front, but rotate them on y
ZStack {
Color.clear
.glassBackgroundEffect(displayMode: leadingDisplayMode)
.padding(padding)
Spacer()
Color.clear
.glassBackgroundEffect(displayMode: trailingDisplayMode)
.padding(padding)
}
.rotation3DLayout(.degrees(90), axis: .y)
// Front
Color.clear
.glassBackgroundEffect(displayMode: frontDisplayMode)
.padding(padding)
}
}
// Create the top and bottom with another overlay and rotate the panes on the X axis
.spatialOverlay {
ZStack {
Color.clear
.glassBackgroundEffect(displayMode: bottomDisplayMode)
.padding(padding)
Spacer()
Color.clear
.glassBackgroundEffect(displayMode: topDisplayMode)
.padding(padding)
}
.rotation3DLayout(.degrees(90), axis: .x)
}
}
}The rest of the lab shows a usage example and adds some buttons to toggle each value of the edge set.
struct Lab078: View {
@State var panes: Edge3D.Set = [.all]
@State var padding: CGFloat = 24
var body: some View {
VStack {
ModelViewSimple(name: "StepCube")
.frame(width: 400, height: 400)
.frame(depth: 400)
.padding3D(.all, 50)
.glassBackgroundBox(padding: padding, panes)
}
.ornament(attachmentAnchor: .scene(.trailing), contentAlignment: .leading, ornament: {
VStack(spacing: 6) {
Slider(value: $padding,
in: 0...96,
step: 1,
minimumValueLabel: Text("0"),
maximumValueLabel: Text("96"),
label: {
Text("Padding")
})
.frame(width: 200)
HStack {
Button(action: {
if panes.contains(.vertical) {
panes.remove(.vertical)
} else {
panes.insert(.vertical)
}
}, label: {
Text("Vertical")
.frame(width: 80)
})
Button(action: {
if panes.contains(.top) {
panes.remove(.top)
} else {
panes.insert(.top)
}
}, label: {
Text("Top")
.frame(width: 80)
})
Button(action: {
if panes.contains(.bottom) {
panes.remove(.bottom)
} else {
panes.insert(.bottom)
}
}, label: {
Text("Bottom")
.frame(width: 80)
})
}
HStack {
Button(action: {
if panes.contains(.horizontal) {
panes.remove(.horizontal)
} else {
panes.insert(.horizontal)
}
}, label: {
Text("Horizontal")
.frame(width: 80)
})
Button(action: {
if panes.contains(.leading) {
panes.remove(.leading)
} else {
panes.insert(.leading)
}
}, label: {
Text("Leading")
.frame(width: 80)
})
Button(action: {
if panes.contains(.trailing) {
panes.remove(.trailing)
} else {
panes.insert(.trailing)
}
}, label: {
Text("Trailing")
.frame(width: 80)
})
}
HStack {
Button(action: {
if panes.contains(.depth) {
panes.remove(.depth)
} else {
panes.insert(.depth)
}
}, label: {
Text("Depth")
.frame(width: 80)
})
Button(action: {
if panes.contains(.front) {
panes.remove(.front)
} else {
panes.insert(.front)
}
}, label: {
Text("Front")
.frame(width: 80)
})
Button(action: {
if panes.contains(.back) {
panes.remove(.back)
} else {
panes.insert(.back)
}
}, label: {
Text("Back")
.frame(width: 80)
})
}
Button(action: {
if panes.contains(.all) {
panes.remove(.all)
} else {
panes.insert(.all)
}
}, label: {
Text("All")
.frame(width: 80)
})
}
.controlSize(.small)
.padding()
.glassBackgroundEffect()
})
}
}Updated 2026.07.29 – Replaced 3D asset with StepCube
Support our work so we can continue to bring you new examples and articles.

Follow Step Into Vision