Let's say I've created a scene with 3 models inside side by side. Now upon user interaction, I'd like to change these models to another model (that is also in the same reality composer pro project). Is that possible? How can one do that?
One way I can think of is to just load all the individual models in RealityView and then just toggle the opacity to show/hide the models. But this doesn't seem like the right way for performance/memory reasons.
How do you swap in and out usdz models?
Right now I load a scene using this code:
struct VolumeView: View {
@State private var show = false
@State var car: Entity?
@State var plane: Entity?
var body: some View {
VStack {
Toggle("Show/Hide", isOn: $show)
.onChange(of: show) { _, newValue in
if newValue {
} else {
}
}.padding()
RealityView { content in
await plane = try? Entity(named: "Plane",
in: realityKitContentBundle)
await car = try? Entity(named: "Car",
in: realityKitContentBundle)
if let plane = plane, let car = car {
content.add(car)
content.add(plane)
}
} update: { content in
if show {
/// show new model somehow
} else {
/// revert to old model someohow
}
}
}
}
}
You haven't provided much information about behavior but update
would get called each time show
is changed so you would just have to removeFromParent
and addChild
or content.remove
and content.add
.
Which is right depends on if you want to swap children from the parent or if you want to work with content
directly.
Here is some swapping logic.
extension Entity {
func swap(_ first: String, name second: String, isFirst: Bool) async throws {
switch isFirst {
case true:
switch self.findEntity(named: first){
case .none:
children.first?.removeFromParent()
let entity = try await Entity(named: first)
self.addChild(entity)
case .some(_):
break //Do nothing
}
case false:
switch self.findEntity(named: second){
case .none:
children.first?.removeFromParent()
let entity = try await Entity(named: second)
self.addChild(entity)
case .some(_):
break //Do nothing
}
}
}
}
import SwiftUI
import RealityKit
struct SwapEntityView: View {
@State private var show = false
@State private var parent1: Entity = .init()
@State private var parent2: Entity = .init()
var body: some View {
VStack {
Toggle("Show/Hide", isOn: $show)
RealityView { content in
await swapParent1(show: show)
await swapParent2(show: show)
content.add(parent1)
content.add(parent2)
} update: { content in
Task { @MainActor in
await swapParent1(show: show)
await swapParent2(show: show)
}
}
}
}
func swapParent1(show: Bool) async {
do {
try await parent1.swap("Earth", name: "Mars", isFirst: show)
} catch {
print(error)
}
}
func swapParent2(show: Bool) async {
do {
try await parent1.swap("RocketToy1", name: "ToyBiplane", isFirst: show)
} catch {
print(error)
}
}
}
And the content
approach is as follows.
import SwiftUI
import RealityKit
struct AddRemoveView: View {
@State private var show = false
@State private var planet: Entity?
@State private var plane: Entity?
var body: some View {
VStack {
Toggle("Show/Hide", isOn: $show)
RealityView { content in
do {
planet = try await Entity(named: "Earth")
plane = try await Entity(named: "ToyBiplane")
guard let plane, let planet else {
return
}
try show(show: show, content: content)
} catch {
print(error)
}
} update: { content in
do {
try show(show: show, content: content)
} catch {
print(error)
}
}
}
}
func show(show: Bool, content: RealityViewContent) throws {
guard let plane, let planet else {return}
switch show {
case true:
content.remove(plane)
content.add(planet)
case false:
content.remove(planet)
content.add(plane)
}
}
}
#Preview {
AddRemoveView()
}
You can also create any combination of these.