简体   繁体   中英

NSTimer update in real time?

I have an NSTimer and I want to update a label in real time which shows a timer's time. The following is my implementation of how I came about doing so:

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
    }
}

As seen by the code, I try to do the following:

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

Which I thought would help me update the cell in real time, however when I view the cells, they are not being updated in real time. When I print out the seconds , minutes , and hours , they are being updated every 1 second which is correct. However, the label is just not updating. Any ideas on how to do this?

EDIT: DuncanC's answer has helped a bunch. I want to also delete the timers when their timers go to 0. However, I am getting an error saying that there is an inconsistency when you delete and insert most likely extremely quickly without giving the table view any time to load. Here is my implmentation:

    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."
    }

This is madness:

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
}

Your job in cellForRowAtIndexPath: is to return the cell - now . But setCellContents does not return any cell to offerCellAtIndexPath , so the cell being returned is merely the empty OfferCell from the first line. Moreover, setCellContents cannot return any cell, because it contains an async , which will not run until after it has returned.

You need to start by getting off this triple call architecture and returning the actual cell, now , in cellForRowAtIndexPath: . Then you can worry about the timed updates as a completely separate problem.

You could use a dispatch timer:

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
}

Your problem isn't with timers. Your problem is that you are handling table views totally wrong. In addition to the problems pointed out by Matt in his answer, your timer method refreshView makes no sense.

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

You are looping through the visible cells, asking for the indexPath of each cell, and then asking the table view to give you that cell again. What do you think this will accomplish? (The answer is "nothing useful".)

What you are supposed to do with table views, if you want a cell to update, is to change the data behind the cell (the model) and then tell the table view to update that cell/those cells. You can either tell the whole table view to reload by calling it's reloadData method, or use the 'reloadRowsAtIndexPaths:withRowAnimation:' method to reload only the cells who's data have changed.

After you tell a table view to reload certain indexPaths, it calls your cellForRowAtIndexPath method for the cells that need to be redisplayed. You should just respond to that call and build a cell containing the updated data.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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