RealityKit: caricamento delle scene di Reality Composer con SwiftUI

Aug 17 2020

Sto cercando di caricare diversi modelli sul viso 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]
        })
    }
}

È così che li carico, ma quando la vista della telecamera si apre e carica un modello, gli altri modelli non verranno caricati. Qualcuno mi può aiutare?

Risposte

jlsiewert Aug 19 2020 at 21:42

Quando il valore delle tue Bindingmodifiche, SwiftUI chiama la tua updateUIView(_:,context:)implementazione, il che non viene notato.

Inoltre, non stai memorizzando il file AnyCancellable. Quando il token restituito da sinkviene deallocato, la richiesta verrà annullata. Ciò potrebbe causare errori imprevisti durante il tentativo di caricare modelli più grandi.

Per risolvere entrambi questi problemi, utilizzare un file Coordinator. import UIKit import RealityKit import SwiftUI import Combina 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)
    }
}

Creiamo una Coordinatorclasse nidificata che contiene il AnyCancellabletoken e spostiamo la loadModelfunzione nel file Coordinator. Oltre a SwiftUI View, Coordinatorè un classche vive mentre la tua vista è visibile (ricorda sempre che SwiftUI potrebbe crearti e distruggerti Viewa piacimento, il suo ciclo di vita non è correlato alla "vista" effettiva che viene mostrata sullo schermo).

In out loadModelclass controlliamo due volte che il valore di our Bindingsia effettivamente cambiato in modo da non annullare una richiesta in corso per lo stesso modello quando SwiftUI aggiorna il nostro View, ad esempio a causa di un cambiamento nell'ambiente.

Quindi implementiamo la makeCoordinatorfunzione per costruire uno dei nostri Coordinatoroggetti. Sia in makeUIViewche in updateUIViewchiamiamo la loadModelfunzione sul nostro Coordinator.

Il dimantleUIViewmetodo è facoltativo. Quando Coordinatorviene decostruito token, anche il nostro viene rilasciato, il che farà sì che Combine annulli le richieste in corso.