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.

Follow Step Into Vision