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.
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: "ToyRocket", bundle: realityKitContentBundle)
.frame(width: 500, height: 500)
.frame(depth: 500)
.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()
})
}
}Support our work so we can continue to bring you new examples and articles.
Download the Xcode project with this and many more labs from Step Into Vision.

Follow Step Into Vision