How to read volume snapping state and classification

We can use the surfaceSnappingInfo environment value to access volume snapping data.

Overview

Starting in visionOS 26, users can snap windows and volumes to surfaces. As of visionOS 26 beta 2, Volumes can only snap to horizontal surfaces. We can read surfaceSnappingInfo data from the environment.

Read How to read window snapping state and classification to learn the basics of snapping.

In this example, we’ll listen for changes to snapping info. This volume has a toy plane that floats in the center of the volume. When the user snaps the volume to a surface, we’ll land the toy plane on the bottom of the volume. When the snapped surface is a table, we’ll also hide the wood base entity.

Video demo of a volume snapping to horizontal surfaces.

Example Code

struct Example084: View {
    @Environment(\.surfaceSnappingInfo) private var surfaceSnappingInfo

    @State var shouldLand = false
    @State var showBase = true

    var body: some View {
        RealityView { content in

            guard let scene = try? await Entity(named: "VolumeSnapping", in: realityKitContentBundle) else { return }
            content.add(scene)
            scene.position.y = -0.35

        } update: { content in

            if let toyPlane = content.entities.first?.findEntity(named: "ToyBiplane") {
                Entity.animate(.easeInOut(duration: 0.5), body: {
                    toyPlane.transform.translation.y = shouldLand ? 0 : 0.35
                })
            }

            if let base = content.entities.first?.findEntity(named: "Base") {
                base.isEnabled = showBase
            }

        }
        // Some UI to show the snapping data
        .ornament(attachmentAnchor: .scene(.topBack), ornament: {
            VStack(spacing: 12) {
                Text("Surface Snapping")
                    .font(.extraLargeTitle)
                VStack(spacing: 12) {
                    HStack {
                        Text("Is Snapped:")
                            .font(.largeTitle)
                            .frame(maxWidth: .infinity, alignment: .trailing)
                        Text("\(surfaceSnappingInfo.isSnapped ? "Yes" : "No")")
                            .font(.largeTitle)
                            .frame(maxWidth: .infinity, alignment: .leading)
                    }

                    HStack {
                        Text("Classification:")
                            .font(.largeTitle)
                            .frame(maxWidth: .infinity, alignment: .trailing)
                        Text("\(surfaceSnappingInfo.classification?.description ?? "" )")
                            .font(.largeTitle)
                            .frame(maxWidth: .infinity, alignment: .leading)
                    }
                }
                Spacer()
            }
            .padding()
            .glassBackgroundEffect()
        })

        // Listen for changes to snapping info and update our scene state
        .onChange(of: surfaceSnappingInfo) {
            if (surfaceSnappingInfo.isSnapped && SurfaceSnappingInfo.authorizationStatus == .authorized) {
                // When snapped to a surface
                shouldLand = true
                
                switch surfaceSnappingInfo.classification {
                case .table:
                   // When we snap to a table, hide the base
                    showBase = false
                default:
                    // When we snap to anything else, show the base
                    showBase = true
                }
            } else {
                // When we are not snapped to anything, show the base and let the plane take off
                shouldLand = false
                showBase = true
            }
        }
    }
}

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.

Questions or feedback?