Lab 063 – First look at Entity Observation
Explore the second side of two-way communication between SwiftUI and RealityKit.
Overview
Since visionOS 1, we’ve been able to update RealityKit entities based on changes to data in SwiftUI. We commonly do this with the update closure on RealityView. At WWDC 2025, Apple made it possible for SwiftUI to observe changes to RealityKit entities. We now have two-way communication between these frameworks!
If you haven’t already, please watch the Information flow section of Better together: SwiftUI and RealityKit. This session has a detailed breakdown of how this works and some guidance for avoiding circular changes.
I’ll write some more detailed examples in the near future. In this lab, I just wanted to take a quick look at this feature. I set this up to observe the transform of an entity and update a SwiftUI ornament as that data changes. This example is contrived and not all that practical, but it gets the point across. In a real app, I would not want to update my SwiftUI views this quickly, unless they were showing something that needed to be in sync with the RealityKit data. For example: a 2D map or HUD for a 3D world.
We can use entity.observable to observer a value or set of values. Check the documentation for some notes on these.
// Transform
entity.observable.transform
// Children
entity.observable.children
// Component
entity.observable.componentsVideo Demo
Let’s see this in action. I have a VectorDisplay view that shows the X, Y, and Z values from a vector. We can use the observed position of an entity.
VectorDisplay(title: "Position", vector: entity.observable.position)The subject in this lab has ManipulationComponent so I can easily interact with it. As I move it around, we can see the SwiftUI position values update.
Bonus: We only observe the entity while the manipulation gesture is active. We do this with by setting an option subject value using two of the Manipulation Events. When we are not interacting with the entity we hide the SwiftUI view.
Full Lab Code
struct Lab063: View {
@State private var subject: Entity?
@State private var subjectTransform: Transform = .init()
@State var willBegin: EventSubscription?
@State var willEnd: EventSubscription?
var body: some View {
RealityView { content in
guard let scene = try? await Entity(named: "ObserveEntity", in: realityKitContentBundle) else { return }
content.add(scene)
scene.position.y = -0.4
// Load the subject entity and add the ManipulationComponent so we can interact with it
guard let subject = scene.findEntity(named: "ToyRocket") else { return }
subject.components.set(ManipulationComponent())
// We'll only observe the subject while the Manipulation is active
willBegin = content.subscribe(to: ManipulationEvents.WillBegin.self) { event in
withAnimation {
self.subject = event.entity
}
}
willEnd = content.subscribe(to: ManipulationEvents.WillEnd.self) { event in
withAnimation {
self.subject = nil
}
}
}
.onChange(of: subject?.observable.transform) {
// Do something when the value changes
// Update related views
// Apply side effects (clamping or transforming values, etc)
print("Subject Transform Changed \(String(describing: subject?.observable.transform))")
}
.ornament(attachmentAnchor: .scene(.back), ornament: {
List {
Section("Observed Entity Data", content: {
if let subject = self.subject {
VectorDisplay(title: "Position", vector: subject.observable.position)
VStack(alignment: .leading) {
Text("Rotation \(subject.observable.orientation.angle)")
.fontWeight(.bold)
VectorDisplay(title: "", vector: subject.observable.orientation.axis)
}
VectorDisplay(title: "Scale", vector: subject.observable.scale)
}
})
}
.padding(.top, 12)
.frame(width: 460, height: 500)
.glassBackgroundEffect()
.offset(y: subject != nil ? 0: 500)
.opacity(subject != nil ? 1 : 0)
})
}
}Support our work so we can continue to bring you new examples and articles.
Download the Xcode project with this and many more labs from Step Into Vision.

Follow Step Into Vision