你真的知道如何更新cell上的进度条吗?

我们经常会遇到这样的场景: 在一个TableView上,每个cell都有一个进度条,可能是下载的进度或者音乐播放的进度,我们需要实时地更新这个进度条。是不是听起来很简单?当心,这里有坑!

大多数人首先想到block或者delegate的回调方式来更新进度。想法是对的,但是忽视了一个问题——“Cell是重用的”。当然,你可以说就不重用。不过大多数时候,为了节省内存空间,优化程序性能,还是建议重用cell的。既然cell被重用,那么用刚刚的方法就会遇到一个奇怪的现象:cell0开始更新自己的进度条,上下滚动TableView时发现进度条跑到cell3上更新了。

来看我的Demo:

/*SimulateDownloader.h*/
@protocol DownloadDelegate <NSObject>

- (void)downloadProgress:(float)progress;
- (void)downloadCompleted;

@end


/*SimulateDownloader*/
- (void)startDownload {
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(downLoadTimer) userInfo:nil repeats:YES];
    [self.timer fire];
}

- (void)downLoadTimer {
    static float progress = 0;
    progress += 0.05;
    if (progress > 1.01) {
        if (self.delegate && [self.delegate respondsToSelector:@selector(downloadCompleted)]) {
            [self.delegate downloadCompleted];
        }
    } else {
        if (self.delegate && [self.delegate respondsToSelector:@selector(downloadProgress:)]) {
            [self.delegate downloadProgress:progress];
        }
    }
}

/*ProcessCell.m*/
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
    if (self) {
        
        ...
                
        _downloader = [[SimulateDownloader alloc] init];
        _downloader.delegate = self;
    }
    return self;
}

#pragma mark - DownloadDelegate
- (void)downloadProgress:(float)progress {
    static float oldValue = 0;
    [self setCircleProgressFrom:oldValue To:progress];
    oldValue = progress;
}

- (void)downloadCompleted {
    self.circle.hidden = YES;
    [_btnPlay setImage:[UIImage imageNamed:@"ic_play_transfer"] forState:UIControlStateNormal];
}

运行结果截图如下:

开始下载第2行

[图1,进度条在第2行]

上下滑动TableView后进度条在第3行

[图2,进度条在第3行]

正如我们开始说的,最开始下载第2行,显示进度条,上下滑动TableView,进度条变到第3行了。

试想,假设最开始系统分配了10个cell并复用。当前cell2的地址是0x000222,它的downloader实例地址是0xfff222。此时,downloader的delegate是cell2,但实际上downloader的delegate绑定的是地址为0x000222的对象,并不是cell2本身。当我们滑动TableView时,cell都被重绘,这时候可能恰好cell3重用了0x000222的对象。那么可想而知,下次更新进度时,downloader的delegate指向的就是cell3,所以cell3会显示进度条变化。

为了解决上面的问题,一般主要有两种思路:

  1. cell不重用

    一般在cell数很少的时候可以使用这种方法。比如总共就5个cell,系统开始就分配了5个cell,那么就不会重用cell。也就不会有delegate指向错误cell的情况出现。

  2. downloader与cell持有的Model绑定

    假如每个cell都有一个对应的model数据结构:

    @interface CellModel : NSObject
    
    @property (nonatomic, strong)   NSNumber *modelId;
    @property (nonatomic, assign)   float progress;
    
    @end
    

    我们可以用KVO方式监听每个CellModel的进度,并且用modelId来判断当前的Cell是否在下载状态以及是否被更新。

    稍作修改的代码:

    /*ProgressCell.m*/
    - (void)setLabelIndex:(NSUInteger)index model:(CellModel *)model {
         self.lbRow.text = [NSString stringWithFormat:@"%u",index];
         self.model = model;
         //这里根据model值来绘制UI
         if (model.progress > 0) {
           [_btnPlay setImage:nil forState:UIControlStateNormal];
         } else {
          [_btnPlay setImage:[UIImage imageNamed:@"ic_download_transfer"] forState:UIControlStateNormal];
         }
         //监听progress
         [self.model addObserver:self forKeyPath:@"progress" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionInitial|NSKeyValueObservingOptionOld context:nil];
     }
     //下载器也与model绑定,这样可以通知到准确的model更新
     - (void)simulateDownloadProgress {   
         [_btnPlay setImage:nil forState:UIControlStateNormal];
         [_downloader startDownload:self.model];
     }
     
     - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
     
         CellModel *model = (CellModel *)object;
         //检查是否是自己的model更新,防止复用问题
         if (model.modelId != self.model.modelId) {
             return;
         }
         float from = 0, to = 0;
     
         if ([keyPath isEqualToString:@"progress"]) {
             if (change[NSKeyValueChangeOldKey]) {
                 from = [change[NSKeyValueChangeOldKey] floatValue];
             }
             if (change[NSKeyValueChangeNewKey]) {
                  to = [change[NSKeyValueChangeNewKey] floatValue];
             }
             [self setCircleProgressFrom:from To:to];
         }
     }
     
     /*SimulateDownloader.m*/
     - (void)downLoadTimer {
         static float progress = 0;
         progress += 0.1;
         if (progress > 1.01) {
             //        if (self.delegate && [self.delegate respondsToSelector:@selector(downloadCompleted)]) {
             //            [self.delegate downloadCompleted];
             //        }
             } else {
             //        if (self.delegate && [self.delegate respondsToSelector:@selector(downloadProgress:)]) {
              //            [self.delegate downloadProgress:progress];
             //        }
             //更新Model,会被KVO的监听对象监听到。
                 self.model.progress = progress;
             }
         }
     }
    

当然如果这里是一个音乐播放进度条,我们可以使用一个单例的播放器并与model绑定。cell同样监听model的progress字段,或者在播放器进度更新时发出通知,所有收到通知的cell检测如果更新的model是自己的才更新UI。

总结:

不要对复用的cell直接使用delegate或者block回调来更新进度条,使用回调更新UI时一定记得与cell所持有的数据绑定,并在绘制cell时检测数据的相应字段

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容

  • iOS网络架构讨论梳理整理中。。。 其实如果没有APIManager这一层是没法使用delegate的,毕竟多个单...
    yhtang阅读 5,098评论 1 23
  • 前言 最近忙完项目比较闲,想写一篇博客来分享一些自学iOS的心得体会,希望对迷茫的你有所帮助。博主非科班出身,一些...
    GitHubPorter阅读 1,396评论 9 5
  • *面试心声:其实这些题本人都没怎么背,但是在上海 两周半 面了大约10家 收到差不多3个offer,总结起来就是把...
    Dove_iOS阅读 27,036评论 29 470
  • 1.自定义控件 a.继承某个控件 b.重写initWithFrame方法可以设置一些它的属性 c.在layouts...
    圍繞的城阅读 2,958评论 2 4
  • 傍晚下班回家的路上,无意间听到商场播放的一首歌,田馥甄低沉婉转的声音由远及近: 对着镜子我承诺 迟早我会还这张脸一...
    甄辛阅读 746评论 0 1