简体   繁体   English

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

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

Any idea why my label.text is only updating when the count finishes?知道为什么我的 label.text 仅在计数完成时更新吗?

didSet is called. didSet被调用。 But the label.text = String(counter) appears to do nothing.但是label.text = String(counter)似乎什么也没做。

Swift 5 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 code is called from the Main Thread.从主线程调用didSet代码。 It is all wired correctly with Storyboards ( not SwiftUI ).它与故事板(不是SwiftUI )正确连接。

You can see the didSet code is called.可以看到调用了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

It looks like you're trying to make some kind of counter which starts at 0 and stops at 3. If that is the case you should not call sleep (which blocks the main thread).看起来您正在尝试创建某种从 0 开始并在 3 停止的计数器。如果是这种情况,您不应该调用sleep (这会阻塞主线程)。

edit: apparently the sleep call was added for demonstration purposes?编辑:显然sleep呼叫是为了演示目的而添加的? In any case the reason why your label seems like it is only updating when the count finishes is because the for loop runs too quickly for the UI to update on each counter increment.在任何情况下,您的 label 似乎只在计数完成时才更新的原因是因为for循环运行得太快,UI 无法在每个counter增量时更新。

Rather use Timer :而是使用Timer

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

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

This is based on my rough understanding on what you're aiming to achieve.这是基于我对您要达到的目标的粗略理解。

You could also DispatchQueue.main.asyncAfter :你也可以DispatchQueue.main.asyncAfter

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

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

For short time intervals, the difference between the two approaches is going to be pretty insignificant.对于较短的时间间隔,这两种方法之间的差异将非常微不足道。 For really accurate time counting, one shouldn't rely on either though, but rather use Date with a Timer that fires say every tenth of a second, and updates counter by rounding to the nearest second (for example).对于真正准确的时间计数,一个人不应该依赖任何一个,而是使用Date和一个Timer ,它每十分之一秒触发一次,并通过四舍五入到最接近的秒来更新计数器(例如)。

You can achieve it like following你可以像下面这样实现它

@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()
        })

    }
}

Never, ever call sleep in an iOS app.永远不要在 iOS 应用程序中调用sleep That will block the main thread, which means your app will be frozen for a whole second with sleep(1) .这将阻塞主线程,这意味着您的应用程序将被sleep(1)冻结一整秒。

This means that the main thread will be blocked while the loop in start_btn finishes and hence the UI can only be updated after the loop has already finished.这意味着主线程将在start_btn中的循环完成时被阻塞,因此 UI 只能在循环完成后更新。

If you want to make the text change every second, modify the button action to如果要使文本每秒更改一次,请将按钮操作修改为

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

and remove sleep(1) from didSet .并从didSet中删除sleep(1)

To avoid UI blocking, dispatch the whole routine to a global queue, and dispatch the UI part to the main queue.为避免 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