美文网首页iOS大咖说iosiOS学习笔记
iOS - 关于每个cell上都出现倒计场景的的研究

iOS - 关于每个cell上都出现倒计场景的的研究

作者: 鲲鹏DP | 来源:发表于2017-06-30 15:13 被阅读1212次

    背景

    以前就有人问过这样一个问题:如果一个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时,会发现一旦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];
        }
    }
    
    • 这样做,会发现当cell移除屏幕,再移回来的时候,不再是从头倒计时,但是多拖动几次又会发现新的问题:显示错乱,某个cell出现在了不该出现的位置.
    • 仔细分析不难发现,cell的复用机制是引起上述现象的"罪魁祸首",要解决这个问题,可以让cell不复用,比方说,可以给每个cell绑定不同的标识,达到不复用的目的,看到这里,如果你也是这么想的,那么最好打住,因为为了达到这个目的,而让cell不复用,以牺牲内存占用为代价,无疑是饮鸩止渴,丢了西瓜,捡个芝麻.
    • 值得的注意的是,如果在cell中实现,每个cell都添加一个定时器,这也是一笔可观的开销.

    2. 在tableView的parentView中实现

    • 既然在cell中实现遇到的坑比较多,那么又想着在外面做.这样有一个明显的好处,就是只需要一个定时器.每次触发,让每个cell上显示的时间递减.由于tableView的显示,取决于传入的数据,只要我在传入数据之前把需要传的数据处理好,这样就不会因为cell的复用机制而带来显示错乱的问题.运行代码,发现问题圆满解决!
    /**定时器触发*/
    - (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时,如果发现定时器不工作,可以用如下方式解决.
       _timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timeChange) userInfo:nil repeats:YES];
                [[NSRunLoop currentRunLoop] addTimer:_timer forMode:UITrackingRunLoopMode];
    
    • ** 关于数据的传入**:
      • 直接提供产品到目前为止还剩多少时间.每个产品对应一个总的时间,用于倒计时.那么,后台给我提供时间时就可以把每个产品对应的总时间返给我们.但是,这样就要求后台自己实时去计算每个产品在我们请求数据时还剩多少时间.
    • 后台把每种产品的截止时间和当前的系统时间返给我们.系统时间,我们可用于矫正自己的系统时间(APP显示的时间是可以人为修改的,并且不通设备之间,iOS与Android之间的时间有可能存在差异,为了统一所以需要矫正),通过矫正好的时间和截止时间,我们就能知道,该产品还剩多少时间.
    • 虽然,我这边自己用的第一种方式写的Demo,但是,相比之下,我更加倾向于第二种数据的传递方式,准确性高,也能为后端同事剩些事.

    4. 后记:由于这只是一个最初的Demo,也只是一些个人的初步看法,难免有些疏漏,如有纰漏,还望指正.

    相关文章

      网友评论

      • iOS_:demo有吗
      • PGOne爱吃饺子:楼主,你好,问个小小的问题
      • quan1991:有demo吗?

      • 南烟客777:这肯定用一个定时器统一处理数据啊,这么多定时器不合理
      • 游小点:用gcd写定时器没有问题
      • Shawn_Locke:我们的项目中也遇到这种需求了,当时用NSTimer貌似总是出现内存泄漏的问题,后来改用GCD的timer貌似就解决了。
        HoyaWhite:GCD的确是正确的,运行时有一个模式。。。
      • Pusswzy:讲的不详细 UITrackingRunLoopMode这个应该好好讲讲
        鲲鹏DP:好吧,这个不是我这次的主要关注点,所以直接略过了.用scheme方式添加的定时器会自动加入runloop,不用UITrackingRunLoopMode,当你拖动cell,此时定时器是会被暂停的,会发现倒计时没有工作.
      • nicoyang:用rac来实现,只需要一个定时器就可以了。每个cell上的显示只是数据的绑定,数据的处理关联到定时器中就好了
        鲲鹏DP:@不落松 KVO监听什么的变化呢?数据吗?
        kirito_song:@鲲鹏DP 如果只要这一个地方、没必要引入rac、等以后想做mvvm再重做一个样。如果想尝试用一个定时器的话、可以试试kvo(还是叫kvc?太久不干iOS记不清了)。效果嘛、和数据绑定一样的。
        鲲鹏DP:好吧,目前还没怎么研究过rac,有时间研究下。
      • 布袋的世界:参考了别人的一个 用单列也是个不错点子
        别问为什么:@鲲鹏DP
        http://www.jianshu.com/p/af62a56ef7e2
        可以参考这个...
        布袋的世界:@鲲鹏DP 写在丹咧 不在cell 改天偶发个 也是个大神写的 偶用在实际工程中
        鲲鹏DP:cell 写成单例?

      本文标题:iOS - 关于每个cell上都出现倒计场景的的研究

      本文链接:https://www.haomeiwen.com/subject/pmmbcxtx.html