0

I'm implementin ARKit and RealityKit, from ARKit I need the estimated light and that value has to goes back to the swiftui view. But as I'm doing each time the swiftui view is redrawn also in the UIViewRepresentable the delegate call updateUIView method. I'm wondering if there's a way to make it not calling the updateUIView method also because the value is written by ARKit and the SwiftUI view doesn't has to modify it.

Here the actual code

struct ARViewContainer : UIViewRepresentable {
    
    @Binding var light: Float
    @Binding var objectToPlace: ObjectModel?
    @Binding var visualizationMode: VisualizationMode
    
    func makeUIView(context: Context) -> ARView {
        print("makeUIView")
        let arView = CustomArView(frame: .zero)
        context.coordinator.view = arView
        arView.session.delegate = context.coordinator
        
        
        return arView
    }
    
    
    //SwiftUI to UIKit
    func updateUIView(_ uiView: ARView, context: Context) {
        print("updateUIView")
        if visualizationMode == .model {
            print("updateUIView")
            uiView.session.pause()
            uiView.session.delegate = nil
            uiView.scene.anchors.removeAll()
            uiView.removeFromSuperview()
            uiView.window?.resignKey()
        }
        if let model = self.objectToPlace {
            if let anchor = uiView.scene.anchors.first(where: { $0.name == "default" }) {
                
                print("removing all")
                anchor.children.removeAll(preservingWorldTransforms: false)
                
            }
            
            if let modelEntity = model.entity {
                modelEntity.transform.rotation = simd_quatf(real: 1.0, imag: SIMD3<Float>(0.0, 0.0, 0.0))
                modelEntity.transform.translation = SIMD3<Float>(0.0, 0.0, 0.0)
                modelEntity.transform.scale = SIMD3<Float>(0.01, 0.01, 0.01)
                let anchorEntity = AnchorEntity(plane: .horizontal)
                anchorEntity.name = "default"
                anchorEntity.addChild(modelEntity)
                modelEntity.generateCollisionShapes(recursive: true)
                uiView.installGestures([.scale, .rotation], for: modelEntity)
                uiView.scene.addAnchor(anchorEntity)
            } else {
                print("not to position")
            }

        }
        
    }
    
    func makeCoordinator() -> Coordinator {
        return Coordinator(light: $light, objectToPlace: $objectToPlace)
    }

    //UIKit to SwiftUI
    class Coordinator: NSObject, ARSessionDelegate {
        var view: ARView?
        @Binding var light: Float
        @Binding var objectToPlace: ObjectModel?

        init(light: Binding<Float>, objectToPlace: Binding<ObjectModel?>) {
            self._light = light
            self._objectToPlace = objectToPlace
        }

        func session(_ session: ARSession, didUpdate frame: ARFrame) {
            light = Float(frame.lightEstimate?.ambientIntensity ?? 0.0)
            if let view {
                for anchor in view.scene.anchors {
                    if anchor.name == "default" {
                        print("trovato")
                        if !anchor.children.isEmpty {
                            objectToPlace = nil
                            
                        }
                    }
                }
            }
            
        }


        func session(_ session: ARSession, didUpdate anchors: [ARAnchor]) {

        }
        
        func sessionShouldAttemptRelocalization(_ session: ARSession) -> Bool {
          return false
        }

    }
    
}

class CustomArView: ARView {
    
    var focusSquare: FocusEntity?

    required init(frame frameRect: CGRect) {
        super.init(frame: frameRect)
        
        focusSquare = FocusEntity(on: self, style: .classic(color: .yellow))
        focusSquare?.delegate = self
        focusSquare?.setAutoUpdate(to: true)
        
        self.setupARView()
    }
    
    @MainActor required dynamic init?(coder decoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func setupARView() {
        let config = ARWorldTrackingConfiguration()
        config.planeDetection = .horizontal
        config.environmentTexturing = .automatic
        config.isLightEstimationEnabled = true
        
        if ARWorldTrackingConfiguration.supportsSceneReconstruction(.mesh) {
            config.sceneReconstruction = .mesh
        }
        
        self.session.run(config)
    }
    
}

and in swiftui

ARViewContainer(light: $vm.light, objectToPlace: $objectToPlace, visualizationMode: $visualizationMode)

the vm.light come from the view model that contains the Published variable

thanks

1 Answers1

0

You need to do a bit more work in updateUIView, you need to check if the UIView is already up to date with each of the representable struct values. Eg check if it is already paused before calling pause.

You also need to set all the bindings again on the coordinator because they will have changed since makeUIView was called. You may want to set them nil first before updating the UIView to prevent getting in an update loop. Another way is to set the delegate nil and then set it back to the coordinator after. This depends on the behaviour of the UIView. Eg UIKit objects don't call their delegates when setting properties but MapKit ones do.

FYI SwiftUI doesn't actually draw anything, it just diffs the View data structs and updates UIView objects with the changes. With UIViewRepresentable you need to update your UIView in the same way it does.

malhal
  • 26,330
  • 7
  • 115
  • 133