swiftuirealitykituiviewrepresentable

RealityKit via UIViewRepresentable freezes when overlayed SwiftUI content changes


Consider a UIViewRepresentable configured like this, for use with RealityKit:

struct ARViewContainer:  UIViewRepresentable {
    let arView = ARView(frame: .zero, cameraMode: ARView.CameraMode.nonAR, automaticallyConfigureSession: false)

    func makeUIView(context: Context) -> ARView {
      //My configurations here
    }

    func updateUIView(_ view: ARView, context: Context) { }
}

If we add a gesture to the SwiftUI view overlaying ARViewContainer and change a @State variable that then alters the view's scaleEffect modifier, the RealityKit scene freezes:

struct ContentView: View {
    @State private var scaleDown: Bool = false

    var body: some View {
        ZStack {
           ARViewContainer()
              .ignoresSafeArea()

           Image("button")
              .resizable()
              .scaleEffect(self.scaleDown ? 0.9 : 1.0)
              .frame(width: 130.0, height: 130.0)
              .position(x: 100.0, y: 100.0)
              .gesture(DragGesture(minimumDistance: 0).onChanged { touch in
                   //This triggers a freeze:
                   self.scaleDown = true   
               }.onEnded { touch in
                   //This triggers a freeze:
                   self.scaleDown = false     
               })
        }
    }
}

I've also found that changing any @Published value from inside the following .subscribe closure results in the same misbehavior -- even if that value is not being applied to anything.

sceneObserver = self.arView.scene.subscribe(to: SceneEvents.Update.self) { context in
   //This would also cause the RealityKit scene to visually freeze, even though somePublishedInteger does nothing but increment:
   somePublishedInteger += 1
}

Tested on real devices: iPhone 14, iPad (9th generation)

Question: What's going wrong, here, and how do I fix it?


Solution

  • Creating an ARView object inside a ContentView does the trick: ARView isn't frozen now.

    import SwiftUI
    import RealityKit
    
    struct ARViewContainer : UIViewRepresentable {
        let arView: ARView
    
        func makeUIView(context: Context) -> ARView {
            let box = ModelEntity(mesh: .generateBox(size: 0.25))
            let anchor = AnchorEntity()
            anchor.addChild(box)
            arView.scene.addAnchor(anchor)
            
            box.move(
                to: Transform(roll: .pi),
                relativeTo: nil,
                duration: 15,
                timingFunction: .linear
            )
            return arView
        }
        func updateUIView(_ view: ARView, context: Context) { }
    }
    

    struct ContentView : View {
        let arView = ARView(
            frame: .zero,
            cameraMode: .nonAR,
            automaticallyConfigureSession: true
        )
        @State var scaleDown: Bool = false
        
        var body: some View {
            ZStack {
                ARViewContainer(arView: arView)
                    .ignoresSafeArea()
    
                Image(systemName: "swift")
                    .resizable()
                    .foregroundStyle(.white)
                    .scaleEffect(scaleDown ? 0.7 : 1.0)
                    .frame(width: 50, height: 50)
                    .position(x: 200.0, y: 100.0)
                    .gesture(
                        DragGesture()
                            .onChanged { _ in
                                scaleDown = true
                                Task {
                                    try await Task.sleep(nanoseconds: UInt64(1e8))
                                    scaleDown = false
                                }
                            }
                    )
            }
        }
    }