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 : .never

Let’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.

Questions or feedback?