Here I have some demo code that is rendering a cylinder "platter" using RealityKit and there is a red circle rendered on top of it which uses Metal and SwiftUI. When the platter appears you will see in the console that makeUIView(context:) is called twice while it is documented that it will only be called once when the view appears for the first time. So this seems like a bug.
If you remove ManipulationComponent from the platter's components you will see that this problem goes away so it seems like that is the cause of the problem. Any insight here would be appreciated! Thank you.
Here is what is printed in the console:
Entity returned from EntityWrapper.makeEntity(context:) was already parented to another entity. This is not supported and may lead to unexpected behavior. SwiftUI adds entities to internally-managed entity hierarchies.
Make UI View! This should be called once.
Make UI View! This should be called once.
Here is the app code:
import SwiftUI
@main
struct SomeApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
ImmersiveSpace(id: "TableTop") {
TableTopPlatterView()
}
}
}
Here is the view code:
import MetalKit
import RealityKit
import SwiftUI
struct ContentView: View {
@Environment(\.openImmersiveSpace)
private var openImmersiveSpace
@Environment(\.dismissImmersiveSpace)
private var dismissImmersiveSpace
@State private var showImmersiveSpace = false
@State private var immersiveSpaceIsOpen = false
var body: some View {
Form {
Toggle("Show table top", isOn: $showImmersiveSpace)
.task(id: showImmersiveSpace) {
if showImmersiveSpace {
await openImmersiveSpace(id: "TableTop")
immersiveSpaceIsOpen = true
} else {
if immersiveSpaceIsOpen {
await dismissImmersiveSpace()
immersiveSpaceIsOpen = false
}
}
}
}
.onDisappear {
// Attempt to close the immersive space on the way out.
Task {
if immersiveSpaceIsOpen {
await dismissImmersiveSpace()
}
}
}
}
}
struct TableTopPlatterView: View {
private var attachmentID: String { "RedCircle" }
var body: some View {
RealityView { content, attachments in
if let redCircleEntity = attachments.entity(for: attachmentID) {
// Lays the red circle in the platter.
let rotation = Rotation3D(redCircleEntity.orientation)
.rotated(by: .init(angle: .degrees(-90), axis: .x))
redCircleEntity.setOrientation(.init(rotation), relativeTo: nil)
redCircleEntity.position.y = 0.026
platterEntity.addChild(redCircleEntity)
content.add(platterEntity)
}
} placeholder: {
ProgressView()
} attachments: {
Attachment(id: attachmentID) {
MetalView()
.clipShape(.circle)
}
}
}
/// The platter entity that the red circle lays on top of.
private let platterEntity: ModelEntity = {
let anchor = AnchorEntity(
.plane(
.horizontal,
classification: .table,
minimumBounds: [0.01, 0.01]
)
)
let material = SimpleMaterial(
color: .lightGray,
roughness: 0.5,
isMetallic: false
)
let platter = ModelEntity(
mesh: .generateCylinder(height: 0.05, radius: 0.475),
materials: [material]
)
platter.generateCollisionShapes(recursive: false)
let components: [any Component] = [
InputTargetComponent(),
GroundingShadowComponent(castsShadow: true),
ManipulationComponent() // MARK: This is causing makeUIView to get called twice!
]
platter.components.set(components)
// Placed closer to the user when booted up.
platter.position = [0, 1, -1.25]
anchor.addChild(platter)
return platter
}()
}
// Metal view that renders a red square.
struct MetalView: UIViewRepresentable {
var device: MTLDevice?
init() {
self.device = MTLCreateSystemDefaultDevice()
}
func makeUIView(context: Context) -> MTKView {
print("Make UI View! This should be called once.")
let mtkView = MTKView()
mtkView.device = device
mtkView.clearColor = MTLClearColor(red: 1.0, green: 0.0, blue: 0.0, alpha: 1.0)
mtkView.delegate = context.coordinator
return mtkView
}
func updateUIView(_ uiView: MTKView, context: Context) { }
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, MTKViewDelegate {
var metalView: MetalView
init(_ metalView: MetalView) {
self.metalView = metalView
}
func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) { }
func draw(in view: MTKView) {
guard let drawable = view.currentDrawable else { return }
guard let descriptor = view.currentRenderPassDescriptor else { return }
let commandQueue = metalView.device?.makeCommandQueue()
let commandBuffer = commandQueue?.makeCommandBuffer()
let renderEncoder = commandBuffer?.makeRenderCommandEncoder(descriptor: descriptor)
renderEncoder?.endEncoding()
commandBuffer?.present(drawable)
commandBuffer?.commit()
}
}
}
2
0
57