繁体   English   中英

Swift didSet 已调用但未更新 UILabel - iOS 属性观察器

[英]Swift didSet called but not updating UILabel - iOS Property observer

知道为什么我的 label.text 仅在计数完成时更新吗?

didSet被调用。 但是label.text = String(counter)似乎什么也没做。

Swift 5

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var label: UILabel!

    var counter:Int = 0 {
        didSet {
            print("old value \(oldValue) and new value: \(counter)")
            label.text = String(counter)
            sleep(1).  // just added to show the label.text is not updating
        }
    }

    @IBAction func start_btn(_ sender: Any) {
        for _ in 1...3 {
            counter += 1
        }
    }
}

从主线程调用didSet代码。 它与故事板(不是SwiftUI )正确连接。

可以看到调用了didSet代码。

old value 0 and new value: 1. Main thread: true
old value 1 and new value: 2. Main thread: true
old value 2 and new value: 3. Main thread: true

看起来您正在尝试创建某种从 0 开始并在 3 停止的计数器。如果是这种情况,您不应该调用sleep (这会阻塞主线程)。

编辑:显然sleep呼叫是为了演示目的而添加的? 在任何情况下,您的 label 似乎只在计数完成时才更新的原因是因为for循环运行得太快,UI 无法在每个counter增量时更新。

而是使用Timer

counter = 0
let timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { timer in
    self.counter += 1

    if self.counter >= 3 {
        timer.invalidate()
    }
}

这是基于我对您要达到的目标的粗略理解。

你也可以DispatchQueue.main.asyncAfter

func countUp() {
    guard counter < 3 else { return }

    DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
        self.counter += 1
        fire()
    }
}

对于较短的时间间隔,这两种方法之间的差异将非常微不足道。 对于真正准确的时间计数,一个人不应该依赖任何一个,而是使用Date和一个Timer ,它每十分之一秒触发一次,并通过四舍五入到最接近的秒来更新计数器(例如)。

你可以像下面这样实现它

@IBAction func start_btn(_ sender: Any) {
    updateCounter()
}

func updateCounter() {
    if counter == 3 {
        return
    } else {
        counter += 1
        DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: {
            self.updateCounter()
        })

    }
}

永远不要在 iOS 应用程序中调用sleep 这将阻塞主线程,这意味着您的应用程序将被sleep(1)冻结一整秒。

这意味着主线程将在start_btn中的循环完成时被阻塞,因此 UI 只能在循环完成后更新。

如果要使文本每秒更改一次,请将按钮操作修改为

@IBAction func start_btn(_ sender: Any) {
    for i in 1...3 {
        DispatchQueue.main.asyncAfter(deadline: .now() + Double(i), execute: {
            self.counter += 1
        })
    }
}

并从didSet中删除sleep(1)

为避免 UI 阻塞,将整个例程分派到全局队列,并将 UI 部分分派到主队列。

import UIKit
class ViewController: UIViewController {

    @IBOutlet weak var label: UILabel!

    var counter:Int = 0 {
        didSet {
            print("old value \(oldValue) and new value: \(counter)")
             DispatchQueue.main.async {
                self.label.text = String(self.counter)
            }
            sleep(1)
        }
    }

    @IBAction func start_btn(_ sender: Any) {
       DispatchQueue.global().async {
        for _ in 1...3 {
            self.counter += 1
          }
        }
    }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM