NSTimer 实时更新?

NSTimer update in real time?

我有一个 NSTimer,我想实时更新一个显示计时器时间的标签。以下是我如何实现的:

override func viewDidAppear(animated: Bool) {
    self.refreshTimer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: "refreshView:", userInfo: nil, repeats: true)
    var currentRunLoop = NSRunLoop()
    currentRunLoop.addTimer(refreshTimer, forMode: NSRunLoopCommonModes)
}

func refreshView(timer: NSTimer){
    for cell in self.tableView.visibleCells(){
        var indexPath = self.tableView.indexPathForCell(cell as! UITableViewCell)
        self.tableView(self.tableView, cellForRowAtIndexPath: indexPath!)
    }
}

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    return offerCellAtIndexPath(indexPath)
}

func offerCellAtIndexPath(indexPath: NSIndexPath) -> OfferCell{
    //Dequeue a "reusable" cell
    let cell = tableView.dequeueReusableCellWithIdentifier(offerCellIdentifier) as! OfferCell
    setCellContents(cell, indexPath: indexPath)
    return cell
}

func setCellContents(cell:OfferCell, indexPath: NSIndexPath!){
    let item = self.offers[indexPath.row]
    var expirDate: NSTimeInterval = item.dateExpired()!.doubleValue
    var expirDateServer = NSDate(timeIntervalSince1970: expirDate)

    //Get current time and subtract it by the predicted expiration date of the cell. Subtract them to get the countdown timer.
    var timer = self.modelStore[indexPath.row] as! Timer
    var timeUntilEnd = timer.endDate.timeIntervalSince1970 - NSDate().timeIntervalSince1970

    if timeUntilEnd <= 0 {
        cell.timeLeft.text = "Finished."
    }
    else{
        //Display the time left
        var seconds = timeUntilEnd % 60
        var minutes = (timeUntilEnd / 60) % 60
        var hours = timeUntilEnd / 3600
        cell.timeLeft.text = NSString(format: "%dh %dm %ds", Int(hours), Int(minutes), Int(seconds)) as String
    }
}

如代码所示,我尝试执行以下操作:

  dispatch_async(dispatch_get_main_queue(), { () -> Void in
        cell.timeLeft.text = NSString(format: "%dh %dm %ds", Int(hours), Int(minutes), Int(seconds)) as String
    })

我认为这可以帮助我实时更新单元格,但是当我查看单元格时,它们并没有实时更新。当我打印出 secondsminuteshours 时,它们每 1 秒更新一次,这是正确的。但是,标签只是没有更新。关于如何做到这一点有什么想法吗?

编辑:DuncanC 的回答帮助了很多人。我还想在计时器变为 0 时删除计时器。但是,我收到一条错误消息,指出当您极有可能非常快速地删除和插入时存在不一致,而没有给 table 视图任何时间来加载。这是我的实现:

    if timeUntilEnd <= 0 {
        //Countdown is done for timer so delete it.
        self.offers.removeAtIndex(indexPath!.row)
        self.tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: UITableViewRowAnimation.Fade)
        self.tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Fade)
        cell.timeLeft.text = "Finished."
    }

这太疯狂了:

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    return offerCellAtIndexPath(indexPath)
}

func offerCellAtIndexPath(indexPath: NSIndexPath) -> OfferCell{
    //Dequeue a "reusable" cell
    let cell = tableView.dequeueReusableCellWithIdentifier(offerCellIdentifier) as! OfferCell
    setCellContents(cell, indexPath: indexPath)
    return cell
}

您在 cellForRowAtIndexPath: 的工作是 return 单元格 - 现在。但是 setCellContents 没有 return 到 offerCellAtIndexPath 的任何单元格,因此被 returned 的单元格只是第一行中的空 OfferCell。此外,setCellContents 不能 return 任何单元格,因为它包含一个 async,它不会 运行 直到 之后它有 returned.

您需要先摆脱这种三重调用架构,然后return进入实际单元,现在,在cellForRowAtIndexPath:。然后你就可以把定时更新当作一个完全独立的问题来担心了。

您可以使用调度计时器:

class func createDispatchTimer(
    interval: UInt64, 
    leeway: UInt64, 
    queue: dispatch_queue_t, 
    block: dispatch_block_t) -> dispatch_source_t?
{
    var timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue)
    if timer != nil
    {
        dispatch_source_set_timer(timer, dispatch_walltime(nil, 0), interval, leeway)
        dispatch_source_set_event_handler(timer, block)

        return timer!
    }

    return nil
}

你的问题不在于计时器。您的问题是您处理 table 视图完全错误。除了马特在他的回答中指出的问题,你的计时器方法 refreshView 没有任何意义。

func refreshView(timer: NSTimer)
{
  for cell in self.tableView.visibleCells()
  {
    var indexPath = self.tableView.indexPathForCell(cell as! UITableViewCell)
    self.tableView(self.tableView, cellForRowAtIndexPath: indexPath!)
  }
}

您正在遍历可见单元格,询问每个单元格的 indexPath,然后要求 table 视图再次为您提供该单元格。你认为这会实现什么? (答案是"nothing useful"。)

如果要更新单元格,您应该对 table 视图执行的操作是更改单元格(模型)后面的数据,然后告诉 table 视图更新 cell/those 个单元格。您可以通过调用它的 reloadData 方法告诉整个 table 视图重新加载,或者使用 'reloadRowsAtIndexPaths:withRowAnimation:' 方法仅重新加载数据已更改的单元格。

在您告诉 table 视图重新加载某些 indexPaths 之后,它会为需要重新显示的单元格调用您的 cellForRowAtIndexPath 方法。您应该只响应该调用并构建一个包含更新数据的单元格。