UITableView从iOS7.0以后增加了estimatedRowHeight 属性及对应的tableView:estimatedHeightForRowAtIndexPath:代理方法。苹果官方文档是这样描述estimatedRowHeight属性的:
文档描述的大概意思是,提供一个非负值预估高度可以提高加载表视图的性能,将一些几何计算的成本从加载时间推迟到滚动时间,当创建self-sizing table view cell的时候,需要设置此属性并使用约束来定义cell size。estimatedRowHeight属性的默认值为UITableViewAutomaticDimension,设置为0时表示禁用此属性。这里要注意一点,estimatedRowHeight在iOS11之前默认值为0,在iOS11之后,默认值为非0值。
上面文档简要说明了为UITableViewCell设置预估高度的一些作用,但它具体有哪些直接的表现呢?Talk is cheap.Show me the code.接下来,我们通过代码来说明一下。
利用KVO对tableView conentSize变化监听
[self.tableView addObserver:self forKeyPath:@"contentSize" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:NULL];
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
if ([keyPath isEqualToString:@"contentSize"]) {
NSLog(@"此时TabbleView contentSize 值== %@", NSStringFromCGSize(self.tableView.contentSize));
}
}
打印tableView代理方法名
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
NSLog(@"%s",__FUNCTION__);
return 5;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *cellId = @"CellID";
UITableViewCell *tableViewCell = [tableView dequeueReusableCellWithIdentifier:cellId];
if (!cell) {
tableViewCell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:cellId];
tableViewCell.backgroundColor = [UIColor redColor];
}
NSLog(@"%s",__FUNCTION__);
return tableViewCell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
NSLog(@"%s",__FUNCTION__);
return 270;
}
//- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath{
// NSLog(@"%s",__FUNCTION__);
// return 200;
//}
现在,我们为上述的tableViewCell设置不同的预估高度,来看一下代理方法中 NSLog的打印情况。
1.estimatedRowHeight设置为0
由于不同的系统版本estimatedRowHeight默认值不同,所以这里直接设置estimatedRowHeight为0,表示禁用此属性。当然,也可以在对应的代理方法进行设置。
-[ViewController tableView:numberOfRowsInSection:]
-[ViewController tableView:heightForRowAtIndexPath:]
-[ViewController tableView:heightForRowAtIndexPath:]
-[ViewController tableView:heightForRowAtIndexPath:]
-[ViewController tableView:heightForRowAtIndexPath:]
-[ViewController tableView:heightForRowAtIndexPath:]
此时TabbleView contentSize == {414, 1350}
-[ViewController tableView:cellForRowAtIndexPath:]
-[ViewController tableView:heightForRowAtIndexPath:]
-[ViewController tableView:cellForRowAtIndexPath:]
-[ViewController tableView:heightForRowAtIndexPath:]
-[ViewController tableView:cellForRowAtIndexPath:]
-[ViewController tableView:heightForRowAtIndexPath:]
从上面打印可以看出,在禁用estimatedRowHeight情况下,tableView:heightForRowAtIndexPath:代理方法先执行了5次,然后才执行了tableView:cellForRowAtIndexPath:代理方法,此时tableView contentSize为{414, 1350}。当我们滚动tableView的时候,tableView contentSize没有变化。
2.tableViewCell 预估高度小于实际高度
我们把上面打印tableView代理方法名
中设置预估高度的tableView:estimatedHeightForRowAtIndexPath:代理方法注释去掉,这时每个tableViewCell的预估值高度为200。其实,这里直接为estimatedRowHeight属性赋值也是一样的,但是为了更全面地演示代码的执行过程,所以直接用代理方法设置tableViewCell 预估高度。
-[ViewController tableView:numberOfRowsInSection:]
-[ViewController tableView:estimatedHeightForRowAtIndexPath:]
-[ViewController tableView:estimatedHeightForRowAtIndexPath:]
-[ViewController tableView:estimatedHeightForRowAtIndexPath:]
-[ViewController tableView:estimatedHeightForRowAtIndexPath:]
-[ViewController tableView:estimatedHeightForRowAtIndexPath:]
此时TableView contentSize == {414, 1000}
-[ViewController tableView:cellForRowAtIndexPath:]
-[ViewController tableView:heightForRowAtIndexPath:]
此时TableView contentSize == {414, 1070}
-[ViewController tableView:cellForRowAtIndexPath:]
-[ViewController tableView:heightForRowAtIndexPath:]
此时TableView contentSize == {414, 1140}
-[ViewController tableView:cellForRowAtIndexPath:]
-[ViewController tableView:heightForRowAtIndexPath:]
此时TableView contentSize == {414, 1210}
从上面打印可以看出,tableView:estimatedHeightForRowAtIndexPath:代理方法先执行了5次,然后才执行了tableView:cellForRowAtIndexPath:和tableView:heightForRowAtIndexPath:代理方法。此时从下往上滚动tableView,tableView contentSize的高度以70的差值的增加。
3.tableViewCell 预估高度大于实际高度
我们把上面打印tableView代理方法名
中设置预估高度的tableView:estimatedHeightForRowAtIndexPath:代理方法的返回值设置为300,大于实际高度270的值。
-[ViewController tableView:numberOfRowsInSection:]
-[ViewController tableView:estimatedHeightForRowAtIndexPath:]
-[ViewController tableView:estimatedHeightForRowAtIndexPath:]
-[ViewController tableView:estimatedHeightForRowAtIndexPath:]
-[ViewController tableView:estimatedHeightForRowAtIndexPath:]
-[ViewController tableView:estimatedHeightForRowAtIndexPath:]
此时TableView contentSize == {414, 1500}
-[ViewController tableView:cellForRowAtIndexPath:]
-[ViewController tableView:heightForRowAtIndexPath:]
此时TableView contentSize == {414, 1470}
-[ViewController tableView:cellForRowAtIndexPath:]
-[ViewController tableView:heightForRowAtIndexPath:]
此时TableView contentSize == {414, 1440}
-[ViewController tableView:cellForRowAtIndexPath:]
-[ViewController tableView:heightForRowAtIndexPath:]
此时TableView contentSize == {414, 1410}
从上面打印可以看出,tableView:estimatedHeightForRowAtIndexPath:代理方法先执行了5次,然后才执行了tableView:cellForRowAtIndexPath和tableView:heightForRowAtIndexPath:代理方法。此时,从下往上滚动tableView,tableView contentSize的高度以30(即:预估高度-实际高度)的差值减少。
4.tableViewCell 预估高度等于实际高度
我们把上面打印tableView代理方法名
中设置预估高度的tableView:estimatedHeightForRowAtIndexPath:代理方法的返回值设置为270,等于实际高度270的值。
-[ViewController tableView:numberOfRowsInSection:]
-[ViewController tableView:estimatedHeightForRowAtIndexPath:]
-[ViewController tableView:estimatedHeightForRowAtIndexPath:]
-[ViewController tableView:estimatedHeightForRowAtIndexPath:]
-[ViewController tableView:estimatedHeightForRowAtIndexPath:]
-[ViewController tableView:estimatedHeightForRowAtIndexPath:]
此时TableView contentSize == {414, 1350}
-[ViewController tableView:cellForRowAtIndexPath:]
-[ViewController tableView:heightForRowAtIndexPath:]
-[ViewController tableView:cellForRowAtIndexPath:]
-[ViewController tableView:heightForRowAtIndexPath:]
-[ViewController tableView:cellForRowAtIndexPath:]
-[ViewController tableView:heightForRowAtIndexPath:]
-[ViewController tableView:heightForRowAtIndexPath:]
-[ViewController tableView:heightForRowAtIndexPath:]
-[ViewController tableView:heightForRowAtIndexPath:]
从上面打印可以看出,tableView:estimatedHeightForRowAtIndexPath:代理方法先执行了5次,然后才执行了tableView:cellForRowAtIndexPath和tableView:heightForRowAtIndexPath:代理方法,此时从下往上滚动tableView,tableView contentSize没有变化。
总结
1.在禁用tableViewCell预估高度的情况下,系统会先把所有tableViewCell实际高度先计算出来,也就是先执行tableView:heightForRowAtIndexPath:代理方法,接着用获取的tableViewCell实际高度总和来参与计算tableView contentSize,然后才显示tableViewCell的内容。在这个过程中,如果实际高度计算比较复杂的话,可能会消耗更多的性能。
2.在使用tableViewCell预估高度的情况下,系统会先执行所有tableViewCell的预估高度,也就是先执行tableView:estimatedHeightForRowAtIndexPath:代理方法,接着用所有tableViewCell预估高度总和来参与计算tableView contentSize,然后才显示tableViewCell的内容。这时候从下往上滚动tableView,当有新的tableViewCell出现的时候,如果tableViewCell预估值高度减去实际高度的差值不等于0,tableView contentSize的高度会以这个差值来动态变化,如果差值等于0,tableView contentSize的高度不再变化。在这个过程中,由之前的所有tableViewCell实际高度一次性先计算变成了现在预估高度一次性先计算,然后实际高度分步计算。正如苹果官方文档所说,减少了实际高度计算时的性能消耗,但是这种实际高度和预估高度差值的动态变化在滑动过快时可能会产生”跳跃“现象,所以此时的预估高度和真实高度越接近越好。
参考文章
http://blog.sunnyxx.com/2015/05/17/cell-height-calculation/