UITableView 小結

1 代理方法的生命週期

  1. 若是不設置estimatedRowHeight (屬性或代理方法),會去獲取全部數據源的cell高度(包括屏幕外,這可能涉及沒必要要的計算)
  2. 設置estimatedRowHeight後,只獲取部分cell高度。
  3. begin/endUpdates也不是刷新整個已加載的cell列表。

1.1 注意事項

  1. 重寫cell的setFrame方法 外部決定內部,不是託管佈局的辦法。
  2. 手動算高度時,須要注意 在cell assemble方法中算好,而不是在heightForRow中用數據源計算。
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [tableView cellForRowAtIndex:indexPath]; //會引起死循環,見第一張圖
}
複製代碼

1.2 Cell結構

  • subview添加到cell.contentView上。
  • 高度,若是cell的style不是UITableViewCellSeparatorStyleNone,那麼cell.contentView的高度比cell的高度少1
  • 背景色,若是設置cell.contentView的背景色,移動後的空白會顯示cell的背景色。使用cell設置時左移或者右移顏色是不會變的。
  • initWithStyle: reuseIdentifier: designated初始化方法

2 一些應用

2.1 Cell中嵌套WebView

若是cell高度和webview有關,須要在加載完更新高度的話web

  1. 注意避免死循環。(1.1節中第2條注意事項)
  2. 不要複用cell,由於加載時間太長,若是複用了一個很長的cell,又加載很慢,會有一個長長的空白,並且還可能抖動嚴重。

2.2 Header中嵌套WebView

如:新聞詳情頁下的推薦文件。 最簡單的作法,在Header中放webView,tableView會本身處理Header的高度。 webView加載完成後,修改webView的frame,而後刷新tableview的高度。objective-c

另外一種作法較繁瑣,體驗也很差。 思路值得借鑑,經過設置ContentInset,讓WebView做爲subView來處理。緩存

//webview
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate;
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
    // 一開始是webview在滾動,tablview並無滾動。
    // 直到webivew到底,其scrollview滑不動了。
    // 此時,tableview的scrollview才接管滑動(tableview在webview下面,因此後接收手勢)
    // 此時若是下滑,由於禁止了webview的scrollview滑動,因此滑動的是tablview的scrollview。
    if(self.tableView.contentOffset.y==0) {
        [self.webView.scrollView setScrollEnabled:YES];
    }else {
        [self.webView.scrollView setScrollEnabled:NO];
    }
}

//tableview
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
    if(self.tableView.contentOffset.y==0) {
        [self.webView.scrollView setScrollEnabled:YES];
    }else {
        [self.webView.scrollView setScrollEnabled:NO];
    }
}
複製代碼

2.3 TableView嵌套TableView,朋友圈的評論

  1. 合理佈局
    • avatar,name,content,展開/摺疊 -> 一個view
    • 圖片,share的文章/視頻/音樂 -> 一個view
    • 點贊、分享、評論 -> 一個view
  2. 根據數據源提早算好cell及三層view的高度
  3. 由於層級較深,用ViewModel很是合適 好比用戶評論時:1 須要彈起鍵盤,2 須要修改數據源。 這兩個操做交給controller,都太麻煩了。 1使用單例鍵盤對象,2相似於MVVM,cell持有本身的數據源、tableview

2.4 section header懸停

  • UITableHeaderView不會懸停。
  • sectionHeader懸停與否,取決於UITableViewStyle
    • Plain,會在窗口頂端懸停,不隨着滑動繼續上移。
    • Grouped,不懸停,隨着滑動繼續上移。
//delegate for headerInSection
-(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
    //返回的view的frame,決定不了真實高度。
}

- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
    //高度由這決定
}


//style是readonly屬性,建立時決定。
//若是是tableviewcontroller,則須要使用xib文件修改。
複製代碼

2.5 Cell須要VC的資源

場景:複雜Cell,層級較深,須要利用VC的資源時。 Cell的內部響應託管給VC。如:頭條刪除新聞要求選緣由的彈框。bash

  1. 經過遞歸nextResponder拿到VC,而後用target:action:綁定到cell的方法
  2. 也能夠用RAC,注意由於複用,每一個Cell只須要綁定一次。

2.6 Cell須要刷新TableView高度

如:微博評論展開、微信增長評論等微信

  1. 經過weak持有tablview
  2. 也能夠經過RAC?

2.7 聊天窗口 & 鍵盤輸入時

修改tableview的bounds。app

2.8 scrollToBottom

微信聊天窗口,點文本框,對話欄不論contentOffset都滾動到底部 主要是contentSize和bounds。oop

- (void)tableViewScrollsToBottom {

    if(self.chatDataArray.count<1){

        return;

    }

    //若是不到一屏,就不拖到最下

    UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:self.chatDataArray.count-1 inSection:0]];

    CGFloat lastCellMaxY = CGRectGetMaxY(cell.frame);

    

    if(cell!=nil && lastCellMaxY<self.tableView.frame.size.height) {

        return;

    }

    

    CGFloat yOffset = 0;

//若是tableview的contentSize超過table的bounds高度,就向上滾動

    if (self.tableView.contentSize.height > self.tableView.bounds.size.height) {

        yOffset = self.tableView.contentSize.height - self.tableView.bounds.size.height;

    }

    [self.tableView setContentOffset:CGPointMake(0, yOffset) animated:NO];

//    SHWCRLog([NSString stringWithFormat:@"after tableView.contentSize %f, contentOffset %f", self.tableView.contentSize.height, self.tableView.contentOffset.y]);

}

複製代碼

2.9 曝光統計

//TableView
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
    AModel *model = self.datas[indexPath.row];
    [cell start_show:model.uiModel];
}
- (void)tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath*)indexPath {
    [cell stop_show];
}

//Cell
- (void)start_show:(AModel*)model {
        NSTimer *timer = [NSTimer timerWithTimeInterval:3
                                             target:self
                                           selector:@selector(firedTimer:)
                                           userInfo:model repeats:NO];
        [[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
        //避免cell複用的問題,使用隊列來管理timer。
        @synchronized(self.timerArray) {
                [self.timerArray addObject:timer];
        }
}

- (void)stop_show {
        @synchronized(self.timerArray) {
                if(self.timerArray.firstObject) {
                        NSTimer *timer = self.timerArray.firstObject;
                        [timer invalidate];
                        [self.timerArray removeObjectAtIndex:0];
                }
        }    
}

- (void)firedTimer:(NSTimer *)timer {
            @synchronized(self.timerArray) {
                        [self.timerArray removeObject:timer];
            }
            ... //埋點
}
複製代碼

2.10 TableView加Header、Footer後與MJRefresh的衝突】

網上查了下,說是由於iOS11中對自動高度的默認值改變形成的。能夠經過關閉高度自動設置修復。 iOS10以後,能夠用自帶的refreshControl,iOS開發之UIRefreshControl使用踩坑佈局

3 Cell高度方案,如無性能問題,委託給tableview

  1. tableview本身估算,其實只是爲了計算contentSize。
  2. tableview會根據autolayout計算cell的實際高度
  3. 若是subview很是複雜,卡頓了,本身作對model進行高度緩存。

3.1 原理

Tableview是一個ScrollView,須要知道contentSize,纔能有合理ScrollView的預估。 那麼,Tableview是如何估計contentSize的呢?性能

  • 1 全部cell都具備固定高度,設置rowHeight屬性
tableView.rowHeight = 88;
tableView.rowHeight = UITableViewAutomaticDimension;
複製代碼

此時,contentSize= cell高度*數據源個數 + header高度等ui

  • 2 cell高度不固定,設置代理方法,此時rowHeight屬性失效。
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    // return xxx
}
複製代碼

在沒有設置estimatedRowHeight時,就會詢問每一個cell的高度。

  • 3 estimatedRowHeight 只算獲取一部分cell高度。

3.2 不須要手動計算高度,只須要緩存高度。

在cell的layoutSubviews佈局完成後,拿到cell的高度(Masonry的話,要加一句[self layoutIfNeeded])。

經常使用API

1 屬性

_tableView.bounces = NO;
        _tableView.estimatedRowHeight = 152;
        _tableView.rowHeight = UITableViewAutomaticDimension;
        _tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
複製代碼

2 只刷新高度

[self.tableView beginUpdates];

[self.tableView endUpdates];

複製代碼

3 去掉分割線

self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;

複製代碼

4 全局刷新、局部刷新

  • 全局刷新: [self.tableView reloadData];

  • 修改局部刷新, reloadRowsAtIndexPaths

  • 刪除局部刷新,deleteRowsAtIndexPaths

5 屏幕顯示的cell

- (NSArray*)visibleCells;
- (NSArray*)indexPathsForVisibleRows;
複製代碼

初始化代碼

//複雜Cell,根據需求看是否持有VC、TableView、VM
@protocol SomeCellDelegate
- (void)onDelClicked:(id)sender;    //cell的內部響應託管給VC。如:頭條刪除新聞要求選緣由的彈框。
@end
@property (nonatomic, strong)SomeModel *model;
@property (nonatomic, weak)UIViewController<SomeCellDelegate> * cellDelegate;
@property (nonatomic, weak)UITableView * tableView;    //如:cell高度變化,要求TableView刷新高度

//TableViewCell
- (void)awakeFromNib {
    [super awakeFromNib];
    [self setUp];
}

- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
    if(self) {
        [self setUp];
    }
    return self;
}

- (void)setUp {
    self.backgroundColor = [UIColor colorWithHexString:[SHWRUMetaHolder inst].feed.bgColor alpha:1];
    [self.contentView addSubview:self.titleLabel];
    [self.contentView addSubview:self.contentImageView];
    [self.contentView addSubview:self.separateLine];
}

- (void)setModel:(SHWRUNewsWrappedModel *)model {
    _model = model;
    [self updateContent];
    [self updateLayout];
}

- (void)updateLayout {
}
- (void)updateContent {
}
複製代碼
相關文章
相關標籤/搜索