【问题标题】:How to update UITableViewCells using NSTimer and NSNotificationCentre in Swift如何在 Swift 中使用 NSTimer 和 NSNotificationCentre 更新 UITableViewCells
【发布时间】:2015-02-16 04:26:45
【问题描述】:

注意:请在 Swift 中寻求答案。

我想做的事:

  • 让表格视图单元格每 1 秒更新一次并实时显示 倒计时。

我目前的情况:

  • 我有一个包含标签的单元格的表格视图。

  • 生成单元格时,它们会调用一个函数来计算今天与存储的目标日期之间的时间,并在标签中显示剩余的倒计时时间。

  • 为了让单元格每秒更新一次标签,我 使用NSTimer 每秒重新加载表格视图。

  • 问题:倒计时有效,但是当我尝试执行滑动删除操作时,因为 NSTimer 重新加载了 tableview,它重置了 滑动单元格使其不再被滑动,这对于最终用户来说当然是不可接受的。

我正在尝试什么/可能的解决方案

  • 我听说更好的方法是使用由NSTimer 触发的NSNotificationCenter 并向每个单元格添加侦听器。每 1 秒从NSNotificationCenter 发送一个“广播”,单元格将收到“广播”并更新标签。

  • 我尝试在生成新单元格的代码部分中为每个单元格添加一个侦听器:

    // Generate the cells
    
    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    
    var cell = tableView.dequeueReusableCellWithIdentifier("Tablecell", forIndexPath: indexPath) as UITableViewCell
    let countdownEntry = fetchedResultController.objectAtIndexPath(indexPath) as CountdownEntry
    
    // MARK: addObserver to "listen" for broadcasts
    NSNotificationCenter.defaultCenter().addObserver(self, selector: "updateCountdownLabel", name: mySpecialNotificationKey, object: nil)
    
    func updateCountdownLabel() {
        println("broadcast received in cell")
        if let actualLabelCountdown = labelCountdown {
            // calculate time between stored date to today's date
            var countdownValue = CountdownEngine.timeBetweenDatesRealTime(countdownEntry.date)
            actualLabelCountdown.text = countdownValue
        }
    }
    
  • 但观察者的选择器似乎只能在视图控制器级别定位功能,而不是在单元格中。 (我无法从单元格内触发“updateCountdownLabel”。当我在视图控制器级别创建同名函数时,可以调用该函数)

问题

  • 所以我的问题是:这是正确的方法吗?
  • 如果是,如何让侦听器在单元级别触发函数?
  • 如果不是,那么在不中断滑动单元格操作的情况下实现此实时倒计时的最佳方法是什么?

提前感谢您的帮助!

【问题讨论】:

  • 你试过用reloadRowsAtIndexPaths(_ indexPaths: [AnyObject], withRowAnimation animation: UITableViewRowAnimation)代替reloadData吗?
  • 这就是我用来重新加载表格单元格的方式func timerAction() { // update the countdown label with the countdown value //tableView.reloadData() } 使用“reloadRowsAtIndexPaths”会避免取消单元格的滑动状态吗?如果我要在这种情况下使用“reloadRowsAtIndexPaths”,我会在哪里调用它?
  • 您可以避免在索引路径列表中滑动单元格。如果您可以直接访问标签来更新倒计时值,则根本不需要调用reload
  • 太棒了。因此,使用这种方法,NSTimer 的选择器将定位一个使用“reloadRowsAtIndexPaths”的函数,该函数也有一个条件:如果滑动,不要重新加载?另外,NSNotificationCentre 是否仍然起作用还是应该被丢弃?

标签: ios uitableview swift nstimer nsnotificationcenter


【解决方案1】:

这可能是一个根本不重新加载单元格的解决方案。只需让单元格收听更新通知并相应地更改它们的标签。 我假设你继承 UITableViewCell 并给单元格一个 storedDate 属性。您将在准备单元格时设置该属性。

计时器只会触发通知。

记得在dealloc的通知中心注销单元格

这里有一个简单的例子。

包含您的 TableView 的视图控制器:

class ViewController: UIViewController, UITableViewDataSource {

    @IBOutlet weak var tableView: UITableView!

    var timer: NSTimer!


    //MARK: UI Updates

    func fireCellsUpdate() {
        let notification = NSNotification(name: "CustomCellUpdate", object: nil)
        NSNotificationCenter.defaultCenter().postNotification(notification)
    }

    //MARK: UITableView Data Source

    func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        return 1
    }

    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 10
    }

    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cellIdentifier = "CustomCell"
        let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier) as! CustomTableViewCell
        cell.timeInterval = 20
        return cell
    }

    //MARK: View Lifecycle
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        self.timer = NSTimer(timeInterval: 1.0, target: self, selector: Selector("fireCellsUpdate"), userInfo: nil, repeats: true)
        NSRunLoop.currentRunLoop().addTimer(self.timer, forMode: NSRunLoopCommonModes)
    }


    deinit {
        self.timer?.invalidate()
        self.timer = nil
    }

}

自定义单元子类:

class CustomTableViewCell: UITableViewCell {

    @IBOutlet weak var label: UILabel!

    var timeInterval: NSTimeInterval = 0 {
        didSet {
            self.label.text = "\(timeInterval)"
        }
    }

    //MARK: UI Updates

    func updateUI() {
        if self.timeInterval > 0 {
            --self.timeInterval
        }
    }

    //MARK: Lifecycle

    override func awakeFromNib() {
        super.awakeFromNib()
        // Initialization code

        let notificationCenter = NSNotificationCenter.defaultCenter()
        notificationCenter.addObserver(self, selector: Selector("updateUI"), name: "CustomCellUpdate", object: nil)
    }

    deinit {
        NSNotificationCenter.defaultCenter().removeObserver(self)
    }

}

我很确定这个示例不符合您应用的逻辑。只是展示事物是如何发光在一起的。

【讨论】:

  • 这是我试图做的,但我不确定我是否正确地将侦听器分配给单元格。 // MARK: addObserver to "listen" for broadcasts NSNotificationCenter.defaultCenter().addObserver(self, selector: "updateCountdownLabel", name: mySpecialNotificationKey, object: nil) 我正在使用 Core Data 来存储日期,是的,我为单元格提供了该日期的属性。侦听器成功接收到通知,但我不知道如何定位单元格内的函数来更新日期标签。选择器只能针对 ViewController 级别的函数...
  • 当监听器收到通知时——我应该把选择器的目标函数放在哪里?在单元格还是在视图控制器级别?
  • 谢谢!我对 Swift 还是很陌生,所以示例代码真的很有帮助。我将在今晚晚些时候尝试此操作并将结果发回。
  • 当我更改为自定义单元格时,我不再能够访问 Core Data。如果可能,可以在这里使用您的帮助:stackoverflow.com/questions/28653764/… 谢谢!
  • 在 Swift 4 中发生了很多变化,但它仍然有效 :)
【解决方案2】:

正如伊恩·麦克唐纳 (Ian MacDonald) 所建议的,如果您正在刷卡,则应避免在计时器计时结束时重新加载单元格。同时删除 NSNotification,因为它和计时器本质上是在做同样的事情

【讨论】:

  • 如果我放弃NSNotification 并严格使用NSTimer 来更新单元格,那么我假设NSTimer 调用了一个循环遍历每个单元格并更新其标签的函数?
猜你喜欢
  • 1970-01-01
  • 2014-08-13
  • 1970-01-01
  • 2015-03-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多