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วัตถุของเรา ทั้งในmakeUIViewและในupdateUIViewเราเรียกใช้loadModelฟังก์ชันในCoordinatorไฟล์.

dimantleUIViewวิธีการเป็นตัวเลือก เมื่อCoordinatorได้รับการแยกโครงสร้างเราtokenจะได้รับการเผยแพร่เช่นกันซึ่งจะทำให้รวมเข้ากับการยกเลิกคำขอที่กำลังดำเนินอยู่