visionOS 27 introduces ClippingComponent and enhanced ManipulationComponent placement patterns that enable teams to collaboratively explore complex 3D assemblies via SharePlay โ pulling apart sub-assemblies, cross-sectioning models with interactive clipping planes, and auto-expanding hierarchical structures in shared space.
โข ClippingComponent (new in visionOS 27) lets you expose the interior of any 3D asset with a live, draggable axis-aligned bounding box โ something impossible on a 2D screen
โข Moving ManipulationComponent between root and child entities at runtime transforms a static model into an individually interactive assembly without changing geometry
โข SharePlay + RealityKit manipulation enables true multi-user spatial design reviews where each collaborator can simultaneously handle different sub-assemblies
Demonstrates attaching ClippingComponent to an AirPods Pro-style model entity and toggling between off, on, and editing states, with a draggable +Z clipping plane that progressively reveals the interior.
import SwiftUI
import RealityKit
// MARK: - Clipping state machine
enum ClippingState {
case off, on, editing
}
// MARK: - Custom component that caches last-edited bounds
struct ClippingBoundsCache: Component {
var lastBounds: BoundingBox
init(bounds: BoundingBox) { self.lastBounds = bounds }
}
// MARK: - SwiftUI view driving the experience
struct AssemblyClipperView: View {
@State private var clippingState: ClippingState = .off
@State private var rootEntity: Entity = makeAssembly()
var body: some View {
RealityView { content in
content.add(rootEntity)
// Attach manipulation to root so whole model moves together
rootEntity.components.set(ManipulationComponent())
rootEntity.components.set(InputTargetComponent())
rootEntity.components.set(CollisionComponent(shapes: [.generateBox(size: [0.3, 0.3, 0.3])]))
}
.toolbar {
ToolbarItemGroup(placement: .bottomOrnament) {
Button("Off") { applyClipping(.off) }
Button("Clip") { applyClipping(.on) }
Button("Edit") { applyClipping(.editing) }
}
}
}
// MARK: - State transitions
private func applyClipping(_ newState: ClippingState) {
clippingState = newState
switch newState {
case .off:
// Cache current bounds then remove the clipping component
if let existing = rootEntity.components[ClippingComponent.self] {
rootEntity.components.set(ClippingBoundsCache(bounds: existing.bounds))
}
rootEntity.components.remove(ClippingComponent.self)
case .on:
// Restore or create a default clipping volume
let bounds = rootEntity.components[ClippingBoundsCache.self]?.lastBounds
?? BoundingBox(min: [-0.15, -0.15, -0.15], max: [0.15, 0.15, 0.0])
var clip = ClippingComponent(bounds: bounds)
clip.shouldClipChildren = true // NEW in visionOS 27 โ must be true for child geometry
clip.shouldClipSelf = true
rootEntity.components.set(clip)
case .editing:
// Ensure clipping is active, then push the +Z face outward interactively
applyClipping(.on)
animateClipFaceZ(to: 0.12)
}
}
// Animate the +Z clipping plane to a new position in model-local space
private func animateClipFaceZ(to newMaxZ: Float) {
guard var clip = rootEntity.components[ClippingComponent.self] else { return }
let current = clip.bounds
let updated = BoundingBox(
min: current.min,
max: SIMD3<Float>(current.max.x, current.max.y, newMaxZ)
)
clip.bounds = updated
rootEntity.components.set(clip)
}
}
// MARK: - Build a simple two-piece assembly
private func makeAssembly() -> Entity {
let root = Entity()
root.name = "AirPodsProAssembly"
let colors: [(SIMD3<Float>, String)] = [
([0.9, 0.9, 0.9], "TopEnclosure"),
([0.7, 0.7, 0.75], "BottomEnclosure"),
([0.5, 0.5, 0.55], "LogicBoard")
]
for (index, (color, name)) in colors.enumerated() {
let mesh = MeshResource.generateBox(size: [0.08, 0.04, 0.02])
var mat = SimpleMaterial()
mat.baseColor = .init(tint: .init(
red: Double(color.x), green: Double(color.y), blue: Double(color.z), alpha: 1))
let part = ModelEntity(mesh: mesh, materials: [mat])
part.name = name
part.position = [0, Float(index) * 0.05 - 0.05, 0]
root.addChild(part)
}
return root
}CollisionComponent must be added to every entity that has InputTargetComponent or drag gestures will silently fail; ClippingComponent's shouldClipChildren defaults to false โ you must set it to true to clip child entities; bounding box coordinates are in entity local space, not world space, so you must convert drag gesture deltas from clipping-plane space back to model space before updating bounds; releaseBehavior must be set to .stay or released sub-assemblies will snap back
Requires Apple Vision Pro; SharePlay collaboration requires multiple Vision Pro headsets on the same FaceTime call
More iOS 27 APIs land every week.
Get notified when new capabilities are published โ no noise, just signal.