背景
以前就有人問過這樣一個問題:如果一個tableView的很多或者所有cell上都顯示一個倒計時,該怎么實現? 今天自己恰好也遇到了這樣的需求:很多產品,每個都有一個時限,在時限內才可以申購,過了申購功能就會關閉.簡單描述就是,每個cell上有個倒計時,時間結束與否,點擊cell響應的事件是不一樣的.那么怎么實現呢?下面談談自己的思考過程.
1.Cell內部加一個定時器
- 既然每個cell都有一個倒計時,時間還可能不一樣.根據"高內聚,低耦合"的思想,我首先想著直接讓cell自己來實現倒計時功能:每個cell添加一個NSTimer,沒隔1秒,讓其顯示的時間減少一秒.
- (void)timeChange {
self.totalSeconds --;
if (self.totalSeconds < 0) {
self.timerLabel.text = @"倒計時結束";
return;
}
self.timerLabel.text = [self timeChangeWithSeconds:self.totalSeconds];
}
- (void)setDataDict:(NSDictionary *)dataDict {
_dataDict = dataDict;
NSString *totalTime = dataDict[@"totalTime"];
self.totalSeconds = totalTime.integerValue;
self.timerLabel.text = [self timeChangeWithSeconds:self.totalSeconds];
if (!_timer) {
_timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timeChange) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:_timer forMode:UITrackingRunLoopMode];
}
}
- (NSString*)timeChangeWithSeconds:(NSInteger)seconds {
NSInteger temp1 = seconds/60;
NSInteger temp2 = temp1/ 60;
NSInteger d = temp2 / 24;
NSInteger h = temp2 % 24;
NSInteger m = temp1 % 60;
NSInteger s = seconds %60;
NSString * hour = h< 9 ? [NSString stringWithFormat:@"0%ld",(long)h] :[NSString stringWithFormat:@"%ld",(long)h];
NSString *day = d < 9 ? [NSString stringWithFormat:@"0%ld",(long)d] : [NSString stringWithFormat:@"%ld",(long)d];
NSString *minite = m < 9 ? [NSString stringWithFormat:@"0%ld",(long)m] : [NSString stringWithFormat:@"%ld",(long)m];
NSString *second = s < 9 ? [NSString stringWithFormat:@"0%ld",(long)s] : [NSString stringWithFormat:@"%ld",(long)s];
return [NSString stringWithFormat:@"%@天:%@時:%@分:%@秒",day,hour,minite,second];
}
cel內部定時器.png
- 乍看,好像一切都OK,但是當我們拖動cell時,會發(fā)現一旦cell移除屏幕,再拖回來的時候,又會重頭倒計時.當然,這和我在setDataDict:方法中的賦值方式有關,可以通過totalSeconds這個屬性,保存當前剩余的時間,下一次再進來的時候,去取保存好的值.
- (void)setDataDict:(NSDictionary *)dataDict {
_dataDict = dataDict;
if (self.totalSeconds !=0) {
self.timerLabel.text = [self timeChangeWithSeconds:self.totalSeconds];
}else {
NSString *totalTime = dataDict[@"totalTime"];
self.totalSeconds = totalTime.integerValue;
self.timerLabel.text = [self timeChangeWithSeconds:self.totalSeconds];
}
if (!_timer) {
_timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timeChange) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:_timer forMode:UITrackingRunLoopMode];
}
}
- 這樣做,會發(fā)現當cell移除屏幕,再移回來的時候,不再是從頭倒計時,但是多拖動幾次又會發(fā)現新的問題:顯示錯亂,某個cell出現在了不該出現的位置.
- 仔細分析不難發(fā)現,cell的復用機制是引起上述現象的"罪魁禍首",要解決這個問題,可以讓cell不復用,比方說,可以給每個cell綁定不同的標識,達到不復用的目的,看到這里,如果你也是這么想的,那么最好打住,因為為了達到這個目的,而讓cell不復用,以犧牲內存占用為代價,無疑是飲鴆止渴,丟了西瓜,撿個芝麻.
- 值得的注意的是,如果在cell中實現,每個cell都添加一個定時器,這也是一筆可觀的開銷.
2. 在tableView的parentView中實現
- 既然在cell中實現遇到的坑比較多,那么又想著在外面做.這樣有一個明顯的好處,就是只需要一個定時器.每次觸發(fā),讓每個cell上顯示的時間遞減.由于tableView的顯示,取決于傳入的數據,只要我在傳入數據之前把需要傳的數據處理好,這樣就不會因為cell的復用機制而帶來顯示錯亂的問題.運行代碼,發(fā)現問題圓滿解決!
/**定時器觸發(fā)*/
- (void)timeChange {
NSMutableArray *tempArrM = [NSMutableArray array];
for (NSDictionary *dict in self.dataArr) {
NSString *totalTime = dict[@"totalTime"];
if ([totalTime isEqualToString:@"0"]) {
totalTime = @"0";
}else {
totalTime = [NSString stringWithFormat:@"%ld",totalTime.integerValue -1];
}
[tempArrM addObject:@{@"totalTime":totalTime}];
}
self.dataArr = tempArrM;
[self.pageTableView reloadData];
}
3. 值得注意的幾個地方
- 當我們拖動cell時,如果發(fā)現定時器不工作,可以用如下方式解決.
_timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timeChange) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:_timer forMode:UITrackingRunLoopMode];
- ** 關于數據的傳入**:
- 直接提供產品到目前為止還剩多少時間.每個產品對應一個總的時間,用于倒計時.那么,后臺給我提供時間時就可以把每個產品對應的總時間返給我們.但是,這樣就要求后臺自己實時去計算每個產品在我們請求數據時還剩多少時間.
- 后臺把每種產品的截止時間和當前的系統時間返給我們.系統時間,我們可用于矯正自己的系統時間(APP顯示的時間是可以人為修改的,并且不通設備之間,iOS與Android之間的時間有可能存在差異,為了統一所以需要矯正),通過矯正好的時間和截止時間,我們就能知道,該產品還剩多少時間.
- 雖然,我這邊自己用的第一種方式寫的Demo,但是,相比之下,我更加傾向于第二種數據的傳遞方式,準確性高,也能為后端同事剩些事.