带有ARSCNView的UIViewController在屏幕上呈现之前需要很长时间

时间:2017-11-24 13:51:49

标签: ios scenekit arkit

我有一个UIViewController,它使用ARSCNView并通过Scenekit向它添加一些元素,如下例所示。一切都工作正常,除非我打电话给当前显示这个视图控制器,它需要很长的时间或延迟才能在屏幕上显示。

@IBOutlet var sceneView: ARSCNView!

override func viewDidLoad() {
    super.viewDidLoad()

    sceneView.showsStatistics =  DebugSettings.isDebugActive

    for (index, coach) in coachPositions.enumerated() {
        let coachGeometry = SCNBox(width: 0.1, height: 0.1, length: 0.1, chamferRadius: 0.005)
        let coachNode = TrainEngineNode(position:  SCNVector3Make(0, Float(index) * 0.1, -0.5), geometry: coachGeometry)
        sceneView.scene.rootNode.addChildNode(coachNode)
    }

    self.sceneView.autoenablesDefaultLighting = true
}

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)

    // Create a session configuration
    let configuration = ARWorldTrackingConfiguration()

    // Run the view's session
    sceneView.session.run(configuration)
}

2 个答案:

答案 0 :(得分:7)

正如大家所指出的,延迟是你在设置场景时阻止主线程的事实。所有UI更新都发生在主线程上,并且显示刷新60 times per second,除了可以做120Hz的最新iPad专业版。这意味着您所执行的任何同步工作单元必须在不到1/60 = 0.016667秒内完成。

您的初始化程序TrainEngineNode(position:geometry:)可能正在执行诸如从DAE或OBJ文件加载资源的工作,这可能是所有时间的所在。我这样说是因为如果你改变这一行:

let coachNode = TrainEngineNode(position:  SCNVector3Make(0, Float(index) * 0.1, -0.5), geometry: coachGeometry)

这是我需要做的,因为你的问题中没有提供TrainEngineNode

let coachNode = SCNNode(geometry: coachGeometry)

然后iPhone 7没有明显的延迟。

SceneKit和ARKit不要求您对任何特定线程进行更改,因此只要您遇到这种情况,您就可以将工作卸载到后台队列,或者最好是您管理的串行队列。在适当的时间进行工作有一些注意事项,例如在ARSession完全运行后将对象添加到场景中。对于创建ARKit应用程序时可能遵循的模式的一些优秀想法,我建议使用Apple提供的this sample

简化示例如下:

class SimpleARViewController: UIViewController {

    @IBOutlet weak var sceneView: ARSCNView!
    weak var activityIndicator: UIActivityIndicatorView?

    var coachPositions = [1, 2, 3, 4, 5]

    let updateQueue = DispatchQueue(label: "com.example.apple-samplecode.arkitexample.serialSceneKitQueue")

    override func viewDidLoad() {
        super.viewDidLoad()

        sceneView.alpha = 0
        view.backgroundColor = .black
        sceneView.isPlaying = false
        sceneView.session.delegate = self

        let activityIndicator = UIActivityIndicatorView(activityIndicatorStyle: .whiteLarge)
        activityIndicator.startAnimating()
        activityIndicator.translatesAutoresizingMaskIntoConstraints = false

        view.addSubview(activityIndicator)
        NSLayoutConstraint.activate([view.centerXAnchor.constraint(equalTo: activityIndicator.centerXAnchor, constant: 0),
                                     view.centerYAnchor.constraint(equalTo: activityIndicator.centerYAnchor, constant: 0)])
        self.activityIndicator = activityIndicator

        sceneView.autoenablesDefaultLighting = true
    }

    private func loadScene() {

        SCNTransaction.begin()
        SCNTransaction.disableActions = true

        for (index, _) in self.coachPositions.enumerated() {
            let coachGeometry = SCNBox(width: 0.1, height: 0.1, length: 0.1, chamferRadius: 0.005)

            // Simulates loading time of `TrainEngineNode(position:geometry:)`
            usleep(500000)

            let coachNode = SCNNode(geometry: coachGeometry)
            coachNode.worldPosition = SCNVector3Make(0, Float(index) * 0.1, -0.5)

            self.sceneView.scene.rootNode.addChildNode(coachNode)
        }

        SCNTransaction.commit()
        self.isLoading = false
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

        let configuration = ARWorldTrackingConfiguration()
        sceneView.session.run(configuration)
    }

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)

        sceneView.session.pause()
    }

    var isLoading = true
    var hasLoaded = false
}

extension SimpleARViewController: ARSessionDelegate {

    func session(_ session: ARSession, didUpdate frame: ARFrame) {

        // Waiting until after the session starts prevents objects from jumping around
        if hasLoaded == false {
            hasLoaded = true

            updateQueue.async { [weak self] in
                self?.loadScene()
            }
        } else if isLoading == false {
            guard let activityIndicator = self.activityIndicator else { return }

            DispatchQueue.main.async {
                UIView.animate(withDuration: 0.35, animations: { [weak self] in
                    self?.sceneView.alpha = 1
                    activityIndicator.alpha = 0
                }, completion: { _ in
                    activityIndicator.removeFromSuperview()
                })
            }
        }
    }
}

此代码会产生以下互动:

enter image description here

正如你在gif中看到的那样,视图控制器的显示没有任何延迟,因为所有的加载工作都是如此。现在在我们的专用串行队列上完成。即使这个人为的例子也可以通过将TrainEngineNode的创建与它们被添加到场景的时间分离来进一步改进。为了简洁起见,我保持逻辑与您已经拥有的逻辑类似,同时提供相对强大的初始化。对于3D对象加载的更强大/抽象的实现,我建议查看VirtualObjectLoader以及如何在上述示例项目中使用它。

答案 1 :(得分:1)

如果你的循环需要很长时间,你可以试试这个:

override func viewDidLoad() {
    super.viewDidLoad()

    // start your loading animator

    DispatchQueue.main.async {
        self.sceneView.showsStatistics =  DebugSettings.isDebugActive
        for (index, coach) in self.coachPositions.enumerated() {
            let coachGeometry = SCNBox(width: 0.1, height: 0.1, length: 0.1, chamferRadius: 0.005)
            let coachNode = TrainEngineNode(position:  SCNVector3Make(0, Float(index) * 0.1, -0.5), geometry: coachGeometry)
            self.sceneView.scene.rootNode.addChildNode(coachNode)
        }
        self.sceneView.autoenablesDefaultLighting = true

        // stop loading animator

    }
}

如果不能工作,你可以像这样延迟时间:

override func viewDidLoad() {
    super.viewDidLoad()

    // start your loading animator

    let delayTimeInterval: Double = 0.1 // 0.1 second
    let delayTime = DispatchTime(uptimeNanoseconds: DispatchTime.now().uptimeNanoseconds + UInt64(delayTimeInterval * Double(NSEC_PER_SEC)))
    DispatchQueue.main.asyncAfter(deadline: delayTime) {                            
        self.sceneView.showsStatistics =  DebugSettings.isDebugActive
        for (index, coach) in self.coachPositions.enumerated() {
            let coachGeometry = SCNBox(width: 0.1, height: 0.1, length: 0.1, chamferRadius: 0.005)
            let coachNode = TrainEngineNode(position:  SCNVector3Make(0, Float(index) * 0.1, -0.5), geometry: coachGeometry)
            self.sceneView.scene.rootNode.addChildNode(coachNode)
        }
        self.sceneView.autoenablesDefaultLighting = true

        // stop loading animator
    }
}