RealityKit – Carregando cenas do Reality Composer com SwiftUI

Aug 17 2020

Estou tentando carregar modelos diferentes no rosto usando SwiftUI, RealityKit e 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]
        })
    }
}

É assim que eu os carrego, mas quando a visualização da câmera abre e carrega um modelo, os outros modelos não serão carregados. Alguém pode me ajudar?

Respostas

jlsiewert Aug 19 2020 at 21:42

Quando o valor de suas Bindingalterações, SwiftUI está chamando sua updateUIView(_:,context:)implementação, o que notando.

Além disso, você não está armazenando o arquivo AnyCancellable. Quando o token retornado por sinkfor desalocado, a solicitação será cancelada. Isso pode resultar em falhas inesperadas ao tentar carregar modelos maiores.

Para corrigir esses dois problemas, use um arquivo Coordinator. import UIKit import RealityKit import SwiftUI import Combine import 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)
    }
}

Criamos uma Coordinatorclasse aninhada que contém o AnyCancellabletoken e movemos a loadModelfunção para o arquivo Coordinator. Além de SwiftUI View, Coordinatoré um classque vive enquanto sua visualização está visível (lembre-se sempre de que SwiftUI pode criar e destruir sua Viewvontade, seu ciclo de vida não está relacionado à "visualização" real que é mostrada na tela).

Em loadModelnossa classe, verificamos novamente se o valor de our Bindingrealmente foi alterado para que não cancelemos uma solicitação em andamento para o mesmo modelo quando o SwiftUI atualizar nosso View, por exemplo, devido a uma alteração no ambiente.

Em seguida, implementamos a makeCoordinatorfunção para construir um de nossos Coordinatorobjetos. Tanto in makeUIViewquanto in updateUIViewchamamos a loadModelfunção em nosso Coordinator.

O dimantleUIViewmétodo é opcional. Quando o Coordinatorfor desconstruído, nosso tokentambém será liberado, o que fará com que o Combine cancele as solicitações em andamento.