RealityKit Basics: Using ViewAttachmentComponent
visionOS 26 brings us a new way to create attachments right along side our entities.
Overview
visionOS 26 has a handful of new component. Let’s take a look at ViewAttachmentComponent. We’ll recreate the example from this post, using the new method for creating attachments.
RealityKit Basics: Placing attachments in a scene
First off, it is important to know that we can still create attachments using RealityView. This method involves using a closure to define all the attachments that a given scene will access. We generally do this up front when the view is created, but it is possible to do this at other times. Once an attachment has been added to this closure, it becomes available as an entity in the attachments value inside the RealityView make or update closures. We simply get it by ID and add it to our scene.
Now, with ViewAttachmentComponent, we get more control for when and where to create these attachments. Here is a minimum viable attachment.
let entity = Entity()
let attachment = ViewAttachmentComponent(rootView: SomeView())
entity.components.set(attachment)
content.add(entity)We create an entity, create the attachment component with a view. Then we add the component to the entity and add the entity to our RealityView Content. Structurally speaking, once these are added to a scene nothing has really changed from the previous approach. They are still just entities with components that happen to draw SwiftUI content. The only thing that has changed is the way we create them.
Let’s step through the three signs from the example. We’ll start with the warning sign. This will add the attachment as a top-level entity in the scene.
let warningSign = Entity()
let attachment = ViewAttachmentComponent(rootView: WarningSignView())
warningSign.components.set(attachment)
warningSign.position = [1, 1.2, -2]
content.add(warningSign)Notice that the rootView is set to WarningSignView().
fileprivate struct WarningSignView: View {
var body: some View {
VStack(spacing: 24) {
Text("This scene contains gratuitous warnings")
.font(.system(size: 96, weight: .bold))
.textCase(.uppercase)
.multilineTextAlignment(.center)
}
.padding(24)
.foregroundStyle(.white)
.background(.black)
.clipShape(.rect(cornerRadius: 24.0))
}
}The other two signs create attachments as child entities that are positioned relative to their parent.
Wet Floor Sign
if let wetFloorSign = scene.findEntity(named: "wet_floor_sign") {
// Create an entity and use the new ViewAttachmentComponent
let wetFloorAttachment = Entity()
let attachment = ViewAttachmentComponent(rootView: WetFloorSignView())
wetFloorAttachment.components.set(attachment)
// Add the attachment entity as a child of the wet floor sign
wetFloorSign.addChild(wetFloorAttachment)
// Adjust the transform to position it just in front of the sign
let transform = Transform(scale: .init(repeating: 200), rotation: simd_quatf(Rotation3D(angle: Angle2D(degrees: 11), axis: RotationAxis3D(x: -1, y: 0, z: 0))), translation: [0, 30, 6.7])
wetFloorAttachment.transform = transform
}Traffic Cone
if let trafficCone = scene.findEntity(named: "traffic_cone_02") {
// Create an entity and use the new ViewAttachmentComponent
let traffiConeAttachment = Entity()
let attachment = ViewAttachmentComponent(rootView: TrafficConeView())
traffiConeAttachment.components.set(attachment)
// For this example, we'll add the attachment directly to the scenc content
content.add(traffiConeAttachment)
// Then we'll use the data from the traffic cone entity to determine the transform for the attachment
let transform = Transform(
scale: .init(repeating: 1.0),
rotation: simd_quatf(
Rotation3D(angle: Angle2D(degrees: -24), axis: RotationAxis3D(x: 0, y: 1, z: 0))
),
translation: trafficCone.position + [0, 0.8 , 0]
)
traffiConeAttachment.transform = transform
}As you can see, all three examples use the same Entity > Component pattern we are used to.
When should we use this?
There are a handful of situations when I think we should use this component instead of the builder pattern.
- It can be more convenient and easy to reason about. The attachments are managed just like any other component.
- We can more easily pass data from our entities into the views for our attachments. For example, we may want to pass in a tint color from a material so we can use the same color in the view.
- It can be a lot easier to manage scenes that need to dynamically add and remove entities with attachments. In Project Graveyard, I had to do some extra work to keep track of the gravestone entities and their child attachments. With this new component, I can simplify that code.
Full Example Code
struct Example082: View {
var body: some View {
RealityView { content in
guard let scene = try? await Entity(named: "Caution", in: realityKitContentBundle) else { return }
content.add(scene)
// This lab can only run on version 26 and later
guard #available(visionOS 26.0, *) else {
print("WARNING: This lab requires VisionOS 26.0 or later.")
return
}
// Example 01 - add an attachment as a child of an entity
// Get the sign model from the scene
if let wetFloorSign = scene.findEntity(named: "wet_floor_sign") {
// Create an entity and use the new ViewAttachmentComponent
let wetFloorAttachment = Entity()
let attachment = ViewAttachmentComponent(rootView: WetFloorSignView())
wetFloorAttachment.components.set(attachment)
// Add the attachment entity as a child of the wet floor sign
wetFloorSign.addChild(wetFloorAttachment)
// Adjust the transform to position it just in front of the sign
let transform = Transform(scale: .init(repeating: 200), rotation: simd_quatf(Rotation3D(angle: Angle2D(degrees: 11), axis: RotationAxis3D(x: -1, y: 0, z: 0))), translation: [0, 30, 6.7])
wetFloorAttachment.transform = transform
}
// Example 02 - use an entity to position the attachment. Add the attachment to the scene content
if let trafficCone = scene.findEntity(named: "traffic_cone_02") {
// Create an entity and use the new ViewAttachmentComponent
let traffiConeAttachment = Entity()
let attachment = ViewAttachmentComponent(rootView: TrafficConeView())
traffiConeAttachment.components.set(attachment)
// For this example, we'll add the attachment directly to the scenc content
content.add(traffiConeAttachment)
// Then we'll use the data from the traffic cone entity to determine the transform for the attachment
let transform = Transform(
scale: .init(repeating: 1.0),
rotation: simd_quatf(
Rotation3D(angle: Angle2D(degrees: -24), axis: RotationAxis3D(x: 0, y: 1, z: 0))
),
translation: trafficCone.position + [0, 0.8 , 0]
)
traffiConeAttachment.transform = transform
}
// Example 03 - Add the attachment as a standalone entity
let warningSign = Entity()
let attachment = ViewAttachmentComponent(rootView: WarningSignView())
warningSign.components.set(attachment)
warningSign.position = [1, 1.2, -2]
content.add(warningSign)
}
}
}
fileprivate struct WetFloorSignView: View {
var body: some View {
VStack(spacing: 24) {
Text("CAUTION")
.font(.largeTitle)
ZStack {
Image(systemName: "triangle")
.font(.system(size: 96, weight: .semibold))
Image(systemName: "figure.fall")
.font(.system(size: 42, weight: .heavy))
.offset(y:12)
}
Text("No Floor")
.font(.largeTitle)
}
.foregroundStyle(.black)
.textCase(.uppercase)
.padding()
}
}
fileprivate struct TrafficConeView : View {
var body: some View {
VStack(spacing: 24) {
Text("Watch Out")
.font(.extraLargeTitle)
ZStack {
Image(systemName: "triangle")
.font(.system(size: 96, weight: .semibold))
Image(systemName: "eyes")
.font(.system(size: 36, weight: .heavy))
.offset(y:12)
}
Text("for traffic cones")
.font(.extraLargeTitle)
}
.padding(24)
.foregroundStyle(.white)
.textCase(.uppercase)
.background(.trafficOrange)
.clipShape(.rect(cornerRadius: 24.0))
}
}
fileprivate struct WarningSignView: View {
var body: some View {
VStack(spacing: 24) {
Text("This scene contains gratuitous warnings")
.font(.system(size: 96, weight: .bold))
.textCase(.uppercase)
.multilineTextAlignment(.center)
}
.padding(24)
.foregroundStyle(.white)
.background(.black)
.clipShape(.rect(cornerRadius: 24.0))
}
}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