RealityKit: carga de escenas de Reality Composer con SwiftUI

Aug 17 2020

Estoy tratando de cargar diferentes modelos en la cara usando SwiftUI, RealityKit y ARKit.

struct AugmentedRealityView: UIViewRepresentable {

    @Binding var modelName: String

    func makeUIView(context: Context) -> ARView {
    
        let arView = ARView(frame: .zero)
    
        let configuration = ARFaceTrackingConfiguration()

        arView.session.run(configuration, options: [.removeExistingAnchors, 
                                                    .resetTracking])
    
        loadModel(name: modelName, arView: arView)
    
        return arView
    
    }

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

    private func loadModel(name: String, arView: ARView) {

        var cancellable: AnyCancellable? = nil
    
        cancellable = ModelEntity.loadAsync(named: name).sink(
                 receiveCompletion: { loadCompletion in
            
            if case let .failure(error) = loadCompletion {
                print("Unable to load model: \(error.localizedDescription)")
            }                
            cancellable?.cancel()
        },
        receiveValue: { model in
            
            let faceAnchor = AnchorEntity(.face)
            arView.scene.addAnchor(faceAnchor)
            
            faceAnchor.addChild(model)
            
            model.scale = [1, 1, 1]
        })
    }
}

Así es como los cargo, pero cuando la vista de la cámara se abre y carga un modelo, los otros modelos no se cargarán. ¿Alguien me puede ayudar?

Respuestas

jlsiewert Aug 19 2020 at 21:42

Cuando el valor de sus Bindingcambios, SwiftUI está llamando a su updateUIView(_:,context:)implementación, que lo nota.

Además, no está almacenando el archivo AnyCancellable. Cuando el token devuelto por sinkse desasigna, la solicitud se cancelará. Eso podría resultar en fallas inesperadas al intentar cargar modelos más grandes.

Para solucionar ambos problemas, use un archivo Coordinator. importar UIKit importar RealityKit importar SwiftUI importar Combinar importar ARKit

struct AugmentedRealityView: UIViewRepresentable {
    class Coordinator {
        private var token: AnyCancellable?
        private var currentModelName: String?
        
        fileprivate func loadModel(_ name: String, into arView: ARView) {
            // Only load model if the name is different from the previous one
            guard name != currentModelName else {
                return
            }
            currentModelName = name
            
            // This is optional
            // When the token gets overwritten
            // the request gets cancelled
            // automatically
            token?.cancel()
            
            token = ModelEntity.loadAsync(named: name).sink(
                receiveCompletion: { loadCompletion in
                    
                    if case let .failure(error) = loadCompletion {
                        print("Unable to load model: \(error.localizedDescription)")
                    }
                },
                receiveValue: { model in
                    
                    let faceAnchor = AnchorEntity(.camera)
                    arView.scene.addAnchor(faceAnchor)
                    
                    faceAnchor.addChild(model)
                    
                    model.scale = [1, 1, 1]
                })
            }
        
        fileprivate func cancelRequest() {
            token?.cancel()
        }
    }
    
    @Binding var modelName: String
    
    func makeCoordinator() -> Coordinator {
        Coordinator()
    }
    
    static func dismantleUIView(_ uiView: ARView, coordinator: Coordinator) {
        coordinator.cancelRequest()
    }
    
    func makeUIView(context: Context) -> ARView {
        
        let arView = ARView(frame: .zero)
        
        let configuration = ARFaceTrackingConfiguration()
        
        arView.session.run(configuration, options: [.removeExistingAnchors,
                                                    .resetTracking])
        
        context.coordinator.loadModel(modelName, into: arView)
        
        return arView
        
    }
    
    func updateUIView(_ uiView: ARView, context: Context) {
        context.coordinator.loadModel(modelName, into: uiView)
    }
}

Creamos una Coordinatorclase anidada que contiene el AnyCancellabletoken y movemos la loadModelfunción al archivo Coordinator. Aparte de SwiftUI View, Coordinatores una classque vive mientras su vista está visible (siempre recuerde que SwiftUI puede crear y destruir su vista Viewa voluntad, su ciclo de vida no está relacionado con la "vista" real que se muestra en la pantalla).

En loadModelnuestra clase, verificamos dos veces que el valor de nuestro Bindingrealmente cambió para que no cancelemos una solicitud en curso para el mismo modelo cuando SwiftUI actualice nuestro View, por ejemplo, debido a un cambio en el entorno.

Luego implementamos la makeCoordinatorfunción para construir uno de nuestros Coordinatorobjetos. Tanto en makeUIViewcomo en updateUIViewllamamos a la loadModelfunción en nuestro Coordinator.

El dimantleUIViewmétodo es opcional. Cuando Coordinatorse deconstruye token, también se libera, lo que provocará que Combine cancele las solicitudes en curso.