簡體   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