RealityKit Basics: Loading Entities on Device
We can load entities from our app bundle if we’re not working with Reality Composer Pro.
Overview
Most of the time we use Reality Composer Pro to store and bundle our assets. Xcode converts these assets into .reality files for us.
Read a deep dive into this topic: Compiling .reality: The Technical Reality by Cristian DÃaz.
What if we’re not using Reality Composer Pro? Entity has a number of other methods to load files. The most common one is to asynchronously load a file from a bundle. If we don’t provide a bundle name, this will use the main app bundle.
let entity = try! await Entity(named: "Earth") At WWDC 2023, Apple suggested using .rkasset folders to store our assets in a Swift package. They didn’t go into much detail, but indicated that assets provided in this manner are optimized for visionOS at compile time.
Without bothering with a Swift package, let’s compare the loading time for 40 MB USDZ file. We’ll place one copy in a folder in our app and the second copy in a .rkassets folder. We’ll load them by name.

// Loading a file from the main app bundle
LoadingExample(fileName: "EarthFull") // 1.25 seconds
// Loading a file from an `.rkassets` folder in the main app bundle
LoadingExample(fileName: "EarthRK") // 200 millisecondsThe file loaded from rkassets loaded an entire second faster than the other example.
if you decide not to use Reality Composer Pro for your project, make sure to follow the advice from Apple. Create a dedicated Swift package, then use .rkassets to store your files. Import this package where you need it. This should result in a smaller app bundle and much faster loading times.
Example Code
struct Example142: View {
var body: some View {
TabView {
// Loading a file from the main app bundle
LoadingExample(fileName: "EarthFull")
.tabItem {
Image(systemName: "1.circle")
Text("Standalone Example")
}
// Loading a file from an `.rkassets` folder in the main app bundle
LoadingExample(fileName: "EarthRK")
.tabItem {
Image(systemName: "2.circle")
Text("RK Assets Example")
}
}
}
}
fileprivate struct LoadingExample: View {
@State private var loadDuration: Duration?
private let fileName: String
private let clock = ContinuousClock()
init(fileName: String) {
self.fileName = fileName
}
var body: some View {
RealityView { content in
// If RealityView re-runs, don't re-time/reload.
guard loadDuration == nil else { return }
let start = clock.now
// From a standalone file in the app's main bundle
let entity = try! await Entity(named: fileName)
entity.scale = .init(repeating: 2.0)
content.add(entity)
// Capture elapsed time just after adding to the scene
let elapsed = start.duration(to: clock.now)
await MainActor.run {
loadDuration = elapsed
}
}
.realityViewLayoutBehavior(.fixedSize)
.ornament(attachmentAnchor: .scene(.bottomFront), ornament: {
VStack {
if let loadDuration {
Text("Load: \(format(loadDuration))")
} else {
Text("Loading…")
}
}
.padding()
.glassBackgroundEffect()
})
}
}
fileprivate func format(_ duration: Duration) -> String {
let ms = Double(duration.components.seconds) * 1_000 + Double(duration.components.attoseconds) / 1_000_000_000_000_000
if ms < 1_000 {
return String(format: "%.0f ms", ms)
} else {
return String(format: "%.2f s", ms / 1_000)
}
}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