RealityKit - Chargement de scènes Reality Composer avec SwiftUI

Aug 17 2020

J'essaie de charger différents modèles sur le visage à l'aide de SwiftUI, RealityKit et 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]
        })
    }
}

C'est ainsi que je les charge, mais lorsque la vue de la caméra s'ouvre et charge un modèle, les autres modèles ne seront pas chargés. Est-ce que quelqu'un peut m'aider?

Réponses

jlsiewert Aug 19 2020 at 21:42

Lorsque la valeur de vos Bindingmodifications, SwiftUI appelle votre updateUIView(_:,context:)implémentation, ce qui le note.

De plus, vous ne stockez pas le fichier AnyCancellable. Lorsque le jeton renvoyé par sinkest désalloué, la demande est annulée. Cela pourrait entraîner des échecs inattendus lors de la tentative de chargement de modèles de bière blonde.

Pour résoudre ces deux problèmes, utilisez un fichier Coordinator. importer UIKit importer RealityKit importer SwiftUI importer Combiner importer 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)
    }
}

Nous créons une classe imbriquée Coordinatorqui contient le AnyCancellablejeton et déplaçons la loadModelfonction dans le fichier Coordinator. Autre qu'un SwiftUI View, Coordinatorc'est un classqui vit tant que votre vue est visible (n'oubliez jamais que SwiftUI peut créer et détruire votre Viewà volonté, son cycle de vie n'est pas lié à la "vue" réelle qui s'affiche à l'écran).

Dans loadModelnotre classe, nous vérifions que la valeur de our Bindinga réellement changé afin de ne pas annuler une demande en cours pour le même modèle lorsque SwiftUI met à jour notre View, par exemple en raison d'un changement d'environnement.

Ensuite, nous implémentons la makeCoordinatorfonction pour construire l'un de nos Coordinatorobjets. Dans makeUIViewet dans updateUIViewnous appelons la loadModelfonction sur notre Coordinator.

La dimantleUIViewméthode est facultative. Lorsque le sera Coordinatordéconstruit, notre tokensera également publié, ce qui incitera Combine à annuler les demandes en cours.