RealityKit Basics: RealityView
Exploring a SwiftUI view that contains RealityView content.
Overview
RealityView is the SwiftUI view that bridges the 2D and 3D worlds. We add RealityView to a SwiftUI file just like any other view, but the work we do inside it is in RealityKit.
We can start with the most simple version, the make closure. This will fire only once when the view is created. We create or import assets and entities, then add them to the content.
RealityView { content in
let subjectEntity = Entity()
content.add(subjectEntity)
...
}RealityView has a a few other initializers that expose additional features.
struct Example040: View {
@State var isTransparent = false
var body: some View {
RealityView { content, attachments in
let subjectEntity = Entity()
content.add(subjectEntity)
} update: { content, attachments in
} placeholder: {
} attachments: {
}
}
}We can use the update closure to update our scene graph when our SwiftUI state changes. For example, when isTransparent is modified, this will update the OpacityComponent on our entity.
struct Example040: View {
@State var isTransparent = false
var body: some View {
RealityView { content, attachments in
let subjectEntity = Entity()
content.add(subjectEntity)
...
} update: { content, attachments in
// We'll query the scene graph in content for the entity we want to update
if let subject = content.entities.first(where: { $0.name == "Subject" }) {
// We can set a new instance of the OpacityComponent on the entity. This will replace any existing OpacityComponent. We don't need to remove the old one first.
subject.components.set(OpacityComponent(opacity: isTransparent ? 0.5 : 1.0 ))
}
} placeholder: {
} attachments: {
}
}
}We can pass SwiftUI views into the placeholder closure. This will display while our RealityView content is loading. This can be important when loading large assets or complex scenes.
struct Example040: View {
@State var isTransparent = false
var body: some View {
RealityView { content, attachments in
let subjectEntity = Entity()
content.add(subjectEntity)
...
} update: { content, attachments in
...
} placeholder: {
ProgressView()
} attachments: {
}
}
}Attachments are SwiftUI views that we can display inside our RealityView. We define them with an id and a View in the attachments closure. We can access these in the make or update closures to convert them into entities. We can add them to the root content or add them as children of other entities. Working with attachment is always a two step process.
- Define the attachment with an id and a view
- Get the attachment entity using the id and add it to our scene
struct Example040: View {
@State var isTransparent = false
var body: some View {
RealityView { content, attachments in
let subjectEntity = Entity()
content.add(subjectEntity)
...
// 2. Get the attchment by id, then add it to the scene.
// The attachment is now a RealityView entity that can display SwiftUI content.
if let subjectPanel = attachments.entity(for: "subjectPanel") {
content.add(subjectPanel)
}
} update: { content, attachments in
...
} placeholder: {
...
} attachments: {
// 1. Create an attachment with an id
Attachment(id: "subjectPanel", {
Text("Subject")
})
}
}
}Putting it all together

struct Example040: View {
@State var isTransparent = false
var body: some View {
RealityView { content, attachments in
// wait for 2 seconds so we can see the placeholder view
try? await Task.sleep(for: .seconds(2))
let subjectEntity = Entity()
subjectEntity.name = "Subject"
var material = PhysicallyBasedMaterial()
material.baseColor.tint = .stepRed
material.roughness = 0.5
material.metallic = 0.0
let shape = MeshResource.generateSphere(radius: 0.2)
let model = ModelComponent(mesh: shape, materials: [material])
subjectEntity.components.set(model)
subjectEntity.components.set(HoverEffectComponent())
subjectEntity.components.set(InputTargetComponent())
subjectEntity.components.set(OpacityComponent(opacity: 1.0))
subjectEntity.components.set(CollisionComponent(shapes: [ShapeResource.generateSphere(radius: 0.2)]))
content.add(subjectEntity)
material.baseColor.tint = .stepBlue
let sphere = ModelEntity(
mesh: .generateSphere(radius: 0.03),
materials: [material])
sphere.setPosition([-0.25, 0.2, 0.1], relativeTo: subjectEntity)
subjectEntity.addChild(sphere)
// Get the subject panel from the attachments
// Once loaded we can add attachments to the content or add them as children to other entities
if let subjectPanel = attachments.entity(for: "subjectPanel") {
subjectPanel.setPosition([0, 0.3, 0], relativeTo: subjectEntity)
content.add(subjectPanel)
}
} update: { content, attachments in
// This update closure will fire anytime isTransparent is modififed.
// We'll query the scene graph in content for the entity we want to update
if let subject = content.entities.first(where: { $0.name == "Subject" }) {
// We can set a new instance of the OpacityComponent on the entity. This will replace any existing OpacityComponent. We don't need to remove the old one first.
subject.components.set(OpacityComponent(opacity: isTransparent ? 0.5 : 1.0 ))
}
} placeholder: {
// A SwiftUI view that will display while the RealityView is loading
VStack {
Text("Loading")
.padding(EdgeInsets(top: 6, leading: 12, bottom: 6, trailing: 12))
ProgressView()
}
.font(.largeTitle)
.padding()
.glassBackgroundEffect()
} attachments: {
// A collection of attachments that contain SwiftUI Views. RealityKit will convert each of these into entities that we can place in our scene using the make and update closures.
Attachment(id: "subjectPanel", {
VStack {
HStack {
Image(systemName: "circle.fill")
Text("Subject")
}
.font(.largeTitle)
Toggle(isOn: $isTransparent, label: {
Text("Opacity")
})
.toggleStyle(.button)
}
.padding()
.glassBackgroundEffect()
})
}
}
}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