How to read and use volume clipping margins

Allow SwiftUI or RealityKIt content to take advantage of extra space around a volume.

We can use preferredWindowClippingMargins to request a margin. We can read the actual value directly from the environment using windowClippingMargins.

@Environment(\.windowClippingMargins) private var windowClippingMargins
...
windowClippingMargins.top

This will return values SwiftUI points (CGFloat). We could use that value directly to adapt SwiftUI views. If we’re working with RealityKit, we can use Physical Metric to get a scaler we use for our entities.

@PhysicalMetric(from: .meters) private var points = 1
// When no margins, we want scale 0.5 (default)
// When margins exist, scale up to fill the extra space
let baseScale: Float = 0.5

// Note: We using the same margin on all sides, so just read one of them
let margin = windowClippingMargins.top

// Scale up based on available margin space
let marginScale = Float(margin / points)
let scaleFactor = baseScale + marginScale
scene.scale = .init(repeating: scaleFactor)

In this example, we are scaling a scene to fit in a volume and fill the clipping margin. This is just one way to use this value. We can also read the value on each side of the volume and adapt accordingly. We could show and hide additional entities and visual effects, hide ornaments and controls, etc.

Video Demo

Example Code

Sample code is available in Garden030 in the Step Into Example Projects repo.

struct ContentView: View {

    @Environment(\.windowClippingMargins) private var windowClippingMargins
    @PhysicalMetric(from: .meters) private var points = 1

    @State private var length: CGFloat = 0
    @State private var edges: Edge3D.Set = [.top, .leading, .bottom, .trailing, .back]
    @State private var shouldScaleContent = false

    var body: some View {
        VStack {
            RealityView { content in

                if let scene = try? await Entity(named: "Scene", in: realityKitContentBundle) {
                    content.add(scene)
                }
                         } update: { content in
                 if let scene = content.entities.first {

                     if(!shouldScaleContent) {
                         scene.scale = .init(repeating: 0.5)
                         return
                     }
                     // When no margins, we want scale 0.5 (default)
                     // When margins exist, scale up to fill the extra space
                     let baseScale: Float = 0.5
                     
                     // We using the same margin on all sides, so just read one of them
                     let margin = windowClippingMargins.top

                     // Scale up based on available margin space
                     let marginScale = Float(margin / points)
                     let scaleFactor = baseScale + marginScale
                     scene.scale = .init(repeating: scaleFactor)
                 }
             }
        }
        .preferredWindowClippingMargins(edges, length)
        .debugBorder3D(.white)
        .toolbar {
            ToolbarItem(placement: .bottomOrnament, content: {
                HStack {

                    HStack {
                        EdgeToggleButton(edge: .top, label: "Top", edges: $edges)
                        EdgeToggleButton(edge: .bottom, label: "Bottom", edges: $edges)
                        EdgeToggleButton(edge: .leading, label: "Leading", edges: $edges)
                        EdgeToggleButton(edge: .trailing, label: "Trailing", edges: $edges)
                        EdgeToggleButton(edge: .back, label: "Back", edges: $edges)
                    }

                    Divider()

                    // 640 seems to be the max as of visionOS 26 beta 6
                    Slider(value: $length, in: 0...640, label: {
                        Text("Length")
                    })
                    .frame(width: 200)

                    Button(action: {
                        shouldScaleContent.toggle()
                    }, label: {
                        Label("Scale", systemImage: shouldScaleContent ? "circle.circle.fill" : "circle")
                            .labelStyle(.titleAndIcon)
                    })
                }
            })
        }
    }
}

Support our work so we can continue to bring you new examples and articles.

Questions or feedback?