你真的知道如何更新cell上的進度條嗎?

咱們常常會遇到這樣的場景: 在一個TableView上,每一個cell都有一個進度條,多是下載的進度或者音樂播放的進度,咱們須要實時地更新這個進度條。是否是聽起來很簡單?小心,這裏有坑!bash

大多數人首先想到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行
開始下載第2行

[圖1,進度條在第2行]

上下滑動TableView後進度條在第3行
上下滑動TableView後進度條在第3行

[圖2,進度條在第3行]

正如咱們開始說的,最開始下載第2行,顯示進度條,上下滑動TableView,進度條變到第3行了。ui

試想,假設最開始系統分配了10個cell並複用。當前cell2的地址是0x000222,它的downloader實例地址是0xfff222。此時,downloader的delegate是cell2,但實際上downloader的delegate綁定的是地址爲0x000222的對象,並非cell2自己。當咱們滑動TableView時,cell都被重繪,這時候可能剛好cell3重用了0x000222的對象。那麼可想而知,下次更新進度時,downloader的delegate指向的就是cell3,因此cell3會顯示進度條變化。atom

爲了解決上面的問題,通常主要有兩種思路:spa

  1. cell不重用3d

    通常在cell數不多的時候可使用這種方法。好比總共就5個cell,系統開始就分配了5個cell,那麼就不會重用cell。也就不會有delegate指向錯誤cell的狀況出現。code

  2. downloader與cell持有的Model綁定orm

    假如每一個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時檢測數據的相應字段

相關文章
相關標籤/搜索