running timers in tableViewCells being overwritten

2019-03-03 10:11发布

问题:

I have tried asking this question several times but I haven't been able to explain what is going on. Maybe some screen shots may help. I can only post one because I don't have enough reputation points yet.

screenshot after tableview scroll

You can see that one of the timers (2) has been reset. I have tried to fix this multiple ways without success. Here is the code that puts the timers into the tableview cells:

    -(void) calculateTimer:(NSTimer *)theTimer
{
    self.timerItem = [theTimer userInfo];
        // for date only cell
        if(self.timerItem.timerType == 0){
            [theTimer invalidate];
        }

    for (NRCItemCell *cell in [self.tableView visibleCells])
{
    NSIndexPath *ip = [self.tableView indexPathForCell:cell];
    NSUInteger row = [[[NRCItemStore sharedStore]allItems] indexOfObjectIdenticalTo:self.timerItem];
    if (row == ip.row){

            [self configureTimers:cell forRowAtIndexPath:ip];
            cell.timer.text = self.timerItem.timerOutput;
            cell.timerName.text = self.timerItem.timerName;
        }
}
}


-(void)configureTimers:(NRCItemCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {

    NRCtimerItem *item = [[NRCItemStore sharedStore]allItems][indexPath.row];
    NSInteger timerType = item.timerType;


    // timerType set by TimerTypeTableView Controller as follows:

    // 0 - date
    // 1 - seconds elapsed
    // 2 - minutes elapsed
    // 3 - hours elapsed
    // 4 - days elapsed
    // 5 - months elapsed
    // 6 - years elapsed

    switch (timerType) {
        case 0:{
            NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
            [dateFormatter setDateStyle:NSDateFormatterMediumStyle];
            [dateFormatter setTimeStyle:NSDateFormatterNoStyle];

            NSDate *date = [NSDate date];
            NSString *formattedDateString = [dateFormatter stringFromDate:date];
            item.timerOutput = formattedDateString;
        }
            break;
        case 1:
        {
            NSTimeInterval interval = [self.timerItem.startTime timeIntervalSinceNow];
            interval = (-1 * interval);
            int time = round(interval);
            div_t h = div(time, 3600); //seconds total, divided by 3600 equals
            int hours = h.quot;         // hours, divided by 60 equals
            div_t m = div(h.rem, 60);   // minutes
            int minutes = m.quot;
            int seconds = m.rem;        // and remainder is seconds
            //  NSLog(@"%d:%d:%d", hours, minutes, seconds);
            //NSString *intervalString = [NSString stringWithFormat:@"%ld", (long)time];
            NSString *intervalString = [NSString stringWithFormat:@"%d hours, %d minutes, %d seconds", hours, minutes, seconds];

            NSString *outputString = [intervalString stringByAppendingString:@" ago"];
            item.timerOutput = outputString;

        }
            break;
        case 2:
        {
            NSTimeInterval interval = [self.timerItem.startTime timeIntervalSinceNow];
            interval = (-1 * interval);
            int time = roundf(interval);
            div_t h = div(time, 3600);      // seconds total, divided by 3600 equals
            int hours = h.quot;             // hours, divided by 60 equals
            div_t m = div(h.rem, 60);       // minutes
            int minutes = m.quot;
            NSString *intervalString = [NSString stringWithFormat:@"%d hours, %d minutes", hours, minutes];
            NSString *outputString = [intervalString stringByAppendingString:@" ago"];

            item.timerOutput = outputString;

        }
            break;
        case 3:
        {
            NSTimeInterval interval = [self.timerItem.startTime timeIntervalSinceNow];
            interval = (-1 * interval);
            int time = roundf(interval);
            div_t h = div(time, 3600); // seconds total, divided by 3600 equals
            int hours = h.quot;        // hours
            NSString *intervalString = [NSString stringWithFormat:@"%d hours", hours];
            NSString *outputString = [intervalString stringByAppendingString:@" ago"];

            item.timerOutput = outputString;
        }
            break;
        case 4:
        {
            NSTimeInterval interval = [self.timerItem.startTime timeIntervalSinceNow];
            interval = (-1 * interval);
            int time = roundf(interval);
            div_t h = div(time, 3600); // seconds total, divided by 3600 equals
            int hours = h.quot;        // hours, divided by 24 equals
            div_t d =div(h.rem, 24);   // days
            int days = d.quot;
            NSString *intervalString = [NSString stringWithFormat:@"%d days, %d hours", days, hours];
            NSString *outputString = [intervalString stringByAppendingString:@" ago"];

            item.timerOutput = outputString;
        }
            break;
        case 5:
        {
            NSTimeInterval interval = [self.timerItem.startTime timeIntervalSinceNow];
            interval = (-1 * interval);
            int time = roundf(interval);
            div_t h = div(time, 3600); // seconds total, divided by 3600 equals
            __unused int hours = h.quot;        // hours, divided by 24 equals
            div_t d =div(h.rem, 24);   // days
            int days = d.quot;
            div_t y = div(d.rem, 12);// divided by 12 equals months
            int months = y.quot;
            NSString *intervalString = [NSString stringWithFormat:@"%d months, %d days", months, days];
            NSString *outputString = [intervalString stringByAppendingString:@" ago"];
            item.timerOutput = outputString;
        }
            break;
        case 6:
        {
            NSTimeInterval interval = [self.timerItem.startTime timeIntervalSinceNow];
            interval = (-1 * interval);
            int time = roundf(interval);
            div_t h = div(time, 3600); // seconds total, divided by 3600 equals
            __unused int hours = h.quot;        // hours, divided by 24 equals
            div_t d =div(h.rem, 24);   // days
            int days = d.quot;
            div_t y = div(d.rem, 365);// divided by 365 equals years
            int years = y.quot;
            NSString *intervalString = [NSString stringWithFormat:@"%d years, %d days", years, days];
            NSString *outputString = [intervalString stringByAppendingString:@" ago"];
            item.timerOutput = outputString;
        }
            break;
    }
}

The key is the for(NRCItemCell *cell in [self.tableView visibleCells]) fast enumeration. The idea is to loop through the visible cells and only update a cell if the cell's indexPath is equal to the position of the timer in my datastore. However, it looks like scrolling the tableView causes a mismatch between indexPath and and position of the timer in the datastore so that the wrong cell gets overwritten. I have searched all over for an answer and have tried several different approaches but the solution depends on the subtitle label in my custom cell not being overwritten unless the cell position matches the datastore position (which is the way MVC should work, as I understand it). But using reusable cells and scrolling apparently doesn't work the way I thought it did. If there is a solution, I sure would like the help. Thanks in advance!

回答1:

Your main issue here is the reuse of the cells. Every time you scroll the table the cells are reused with the data of other cells. To keep it short, store your timers data in an array and not in the actual cell.

Importent pointers:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
...
cell.timerView.data = nil; //reset the data in the current timer
myCell *updateCell = (id)[tableView cellForRowAtIndexPath:indexPath];
                    if (updateCell)// Makes sure the cell is still visible
                        updateCell.timerView.data = timersArray[indexpath.row];
                });
}


回答2:

My bad. I found the bug in my code. It wasn't with tableView at all. I was storing the timers in an array, correctly, not in the cell, even though that's what the comments in the code said. The bug was that I was inadvertently firing a timer every pass through the tableView, so these timers would fire at unpredictable times and then my code would overlay the cell for the corresponding timer. A lot of debugging work, but good experience. Code is working now.

Thanks for the comments!