The challenge is this: Assuming I have a button in the scene, on every tap, I spawn a new entity(added as a child entity to the root).
class ViewModel {
var emptyRoot: Entity = Entity()
func addCube() {
let newCube = generateCube()
emptyRoot.addChild(newCube)
// more code....
}
}
On each spawn, I want to add an attachment to the newly spawned cube(i.e. all spawned entities will have an attachment).
Based on my current knowledge, we typically define all attachments in the attachments
closure. However, in my case, I won't know how many entities the user would spawn, so I couldn't define all possible attachments when initializing the RealityView.
RealityView { content, attachments in
// Load initial content (empty scene at this point NO entities in the scene)
content.add(viewModel.emptyRoot)
// At this point I won't have any attachments available.
} update: { updateContent, updateAttachments in
//
} attachments: {
// Based on my knowledge, we have to define all available Attachment here and give them ids
}
Use my code that will help you add cube primitives with 3 (or as many attachments as you want) attachments to your scene. However, nobody forbids you to use the regular text model with zero depth, instead of attachments. In info.plist, set Immersive Space Application Session Role
value for a corresponding key.
import SwiftUI
@main struct AttachmentsApp: App {
var body: some Scene {
ImmersiveSpace(id: "AR_ImmersiveSpace") {
ContentView()
}
}
}
import SwiftUI
import RealityKit
struct ContentView : View {
@State var isClicked: Bool = false
@State var counter: Int = 0
var body: some View {
ZStack {
RealityView { content, attachments in
print("App's started")
} update: { content, attachments in
if isClicked {
let box = ModelEntity(mesh: .generateBox(size: 0.1))
box.position.x = 0.25 * Float(counter)
box.name = "box_\(counter)"
content.add(box)
if let text = attachments.entity(for: "\(counter)") {
text.position.x = box.position.x
text.position.y = 0.12
content.add(text)
}
}
} attachments: {
// hard coding
Attachment(id: "1") { Text("Cube 1").font(.extraLargeTitle) }
Attachment(id: "2") { Text("Cube 2").font(.extraLargeTitle) }
Attachment(id: "3") { Text("Cube 3").font(.extraLargeTitle) }
// etc...
}
}
Button("Add Model with Attachment") {
counter += 1
isClicked = true
Task {
try await Task.sleep(nanoseconds: 10_000)
isClicked = false
}
}
.font(.extraLargeTitle)
}
}
I believe it's the only way to add attachments in RealityView, since all the attachments must be explicitly registered with unique IDs in their own @ViewBuilder which will be executed before the moment RealityView's make {..}
and update {..}
closures are run. There is nothing stopping you from creating 100 or 200 аttachments at once. As I said earlier, an alternative to Attachments is to use RealityKit's Text primitives with a zero extrusion depth.
A solution with text primitives is as simple as that:
} update: { content in
if isClicked {
let box = ModelEntity(mesh: .generateBox(size: 0.1))
box.position.x = 0.25 * Float(counter)
box.name = "box_\(counter)"
content.add(box)
let text = ModelEntity(mesh: .generateText("Cube \(counter)",
extrusionDepth: 0,
font: .boldSystemFont(ofSize: 0.03)))
text.model?.materials = [UnlitMaterial()]
let offset = text.visualBounds(relativeTo: nil).extents.x / 2
text.position.x = box.position.x - offset
text.position.y = 0.12
content.add(text)
}
}
This approach has one undeniable advantage over attachments - here's no hard coding.