【问题标题】:iOS - Swift 5 - Label Text Not Updating within LoopiOS - Swift 5 - 标签文本未在循环内更新
【发布时间】:2020-10-20 14:45:58
【问题描述】:

我正在使用 Scenekit,当我点击屏幕时,我正在尝试从循环中更新 UI 上的标签文本属性。我已验证标签已连接到 StoryBoard 中,我可以从 ViewDidLoad 更新标签。我还验证了循环中的文本值是否存在,因为我可以看到它在控制台中打印。手势识别器的完整代码如下,您可以在底部看到我已经尝试过 GCD,但没有成功。任何帮助表示赞赏。

@objc func sceneTapped(recognizer: UITapGestureRecognizer) {
    
    let KTStoKMperSec = 0.0005144444444444
    let aircraftSpeed = 480.0 * KTStoKMperSec
     
    let quatSun =  simd_quatf(angle: Float(-1.0 / aircraftSpeed * 2 * .pi / 86400.0),
                              axis: simd_float3(x: 0,
                                                y: 1,
                                                z: 0))
    
    for i in 0...geodesic.count-1 {
        
        SCNTransaction.begin()

        let quatPitch =  simd_quatf(angle: Float(-1.0/6371.0),
                                    axis: simd_float3(x: 1,
                                                      y: 0,
                                                      z: 0))

        aircraft.position = geodesic[i].unit * 1.05
        aircraft.localRotate(by: SCNQuaternion(quatPitch.vector.x, quatPitch.vector.y, quatPitch.vector.z, quatPitch.vector.w))

        let SCNQuat = SCNQuaternion(quatSun.vector.x, quatSun.vector.y, quatSun.vector.z, quatSun.vector.w)
        termPathnode.rotate(by: SCNQuat, aroundTarget: SCNVector3(x:0, y:1, z:0))

        lightNode.rotate(by: SCNQuat, aroundTarget: SCNVector3(x:0, y:1, z:0))

        cameraNode.position = aircraft.position.unit*3

        SCNTransaction.commit()


        //print(String("\(aircraft.position.x)"))
        DispatchQueue.main.async {
            //self.dataLabel.setNeedsDisplay()
            self.dataLabel.text =  String("\(aircraft.position.x)")
        }
    }
}

【问题讨论】:

    标签: swiftui label grand-central-dispatch scenekit


    【解决方案1】:

    你没有解释问题是什么,但我猜想什么都没有发生,然后标签被更新(最终)到最后一个值。

    那是因为sceneTapped 是一个手势识别器,这意味着它在主线程上运行。 (所有 UI 代码和事件处理都在主线程上运行。)

    所以sceneTapped 在主线程上循环旋转,排队等待在主线程上运行的块。但是主线程正忙于运行sceneTapped,所以这些块都没有执行。

    最后,sceneTapped 返回,所有排队的块突然一个接一个地执行。

    如果这个for 循环是一个长时间运行的循环,它需要在自己的线程中运行。作为一项规则,您永远不会希望让任何需要花费大量时间来执行的代码在主线程上运行。任何阻塞主线程的代码都会冻结您的 UI 和所有用户交互。

    所以基本上你应该有这样的东西:

    @objc func sceneTapped(recognizer: UITapGestureRecognizer) {
        // ...
        DispatchQueue.global().async {
            // execute long running code in a background thread
            for i in 0...geodesic.count-1 {
                // ...
    
                //print(String("\(aircraft.position.x)"))
                DispatchQueue.main.async {
                    //self.dataLabel.setNeedsDisplay()
                    self.dataLabel.text =  String("\(aircraft.position.x)")
                }
            }
        }
    }
    

    您希望大部分操作在后台线程中运行,执行其操作、计算内容、阻塞、等待等等。

    当它与 UI 相关时,它可以排队一个块以在主线程上运行,主线程仍在(独立地)运行、处理事件、更新视图、重新计算布局,并且通常保持您的应用程序响应。

    最后,您必须确保该后台线程中的所有内容都是线程安全的。

    【讨论】:

    • 谢谢詹姆斯。问题在标题中已经说明。你的解决方案是正确的。 “最后,你必须确保后台线程中的所有内容都是线程安全的。”我是编程新手——你能简要解释一下如何确保“线程安全”吗?
    • 遗憾的是,要彻底解释线程安全需要一整本书。概括地说,你要确保你调用的 API 是线程安全的(可以同时从多个线程中使用),或者你必须确保你只在单个线程中使用它们,并且少数 API 只能是在主线程上使用。在您的情况下,您似乎需要调查 SCNTransaction 以确保它符合此标准。 Google 是您的朋友。
    • 再次感谢詹姆斯。是时候让我对线程进行一些谷歌搜索了!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-07-20
    • 1970-01-01
    • 2016-06-03
    • 2021-06-25
    • 1970-01-01
    • 2011-08-06
    相关资源
    最近更新 更多