RealityKit - загрузка сцен из Reality Composer с помощью SwiftUI

Aug 17 2020

Я пытаюсь загрузить разные модели на лицо с помощью SwiftUI, RealityKit и 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]
        })
    }
}

Я загружаю их так, но когда открывается вид камеры и загружает одну модель, другие модели загружаться не будут. Кто-нибудь может мне помочь?

Ответы

jlsiewert Aug 19 2020 at 21:42

Когда значение ваших Bindingизменений, SwiftUI вызывает вашу updateUIView(_:,context:)реализацию, которая не отмечает.

Кроме того, вы не храните AnyCancellable. Когда токен, возвращенный пользователем, sinkбудет освобожден, запрос будет отменен. Это может привести к неожиданным сбоям при попытке загрузить модели лагеров.

Чтобы решить обе эти проблемы, используйте файл Coordinator. импорт UIKit импорт RealityKit импорт SwiftUI импорт объединение импорта 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)
    }
}

Мы создаем вложенный Coordinatorкласс, содержащий AnyCancellableтокен, и перемещаем loadModelфункцию в Coordinator. За исключением SwiftUI View, Coordinatorэто объект, classкоторый живет, пока ваше представление видно (всегда помните, что SwiftUI может создавать и уничтожать вас Viewпо желанию, его жизненный цикл не связан с фактическим «представлением», отображаемым на экране).

В loadModelнашем классе мы дважды проверяем, Bindingдействительно ли значение нашего изменилось, чтобы мы не отменяли текущий запрос для той же модели, когда SwiftUI обновляет нашу View, например, из-за изменения в среде.

Затем мы реализуем makeCoordinatorфункцию для создания одного из наших Coordinatorобъектов. Как in, так makeUIViewи in updateUIViewмы вызываем loadModelфункцию в нашем Coordinator.

dimantleUIViewМетод является необязательным. Когда Coordinatorобъект будет деконструирован, он также tokenбудет выпущен, что приведет к тому, что Combine будет отменять текущие запросы.