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 {
}
复制代码
相关文章
相关标签/搜索