天天品尝iOS7甜点 :: Day 19 :: UITableView Row Height Estimation

这篇文章是天天品尝iOS7甜点系列的一部分,你可以查看完整的系列目录:天天品尝iOS7甜点


Introduction - 介绍

今天我们将会来查看一个相当小的额外的UIKit API,其中一个就是复杂的表格来影响用户体验。估算行高是在表格的代理方法中的一个额外的方法进行设置,它不仅仅可以返回默认的行高,还可以返回估算的高度来进行替换。我们将会查看为什么这属于一个高级的部分。为了能够能够验证这些,我们需要构建一个简单的应用,它里面有一个表格,可以让我们看到具有估算高度和没有估算高度的示例。

本章的实例程序能够在github上面进行访问,访问地址:github.com/ShinobiControls/iOS7-day-by-day

Without estimation - 没有估算

我们通过UITableViewController来创建一个简单的UITableView.它包含了一个section200行数据。没有单元包含了包含他们自己的索引和高度。如果所有的行都是具有相同高度,那么我们就不需要去实现代理中的heightForRowAtIndexPath:方法,然后我们也不需要去使用新的估算方法来改善什么东西。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
// Retur the number of sections.
return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
// Return the number of rows in the section.
return 200;
}

- (UITableViewCell *)tableView:(UITableView *)tableview cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellIdentifier = @"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];

// Configure the cell...
cell.textLabel.text = [NSString stringWithFormat:@"Cell %03d", indexPath.row];
CGFloat height = [self heightForRowAtIndex:indexPath.row];
cell.detailTextLabel.text = [NSString stringWithFormat:@"Height %0.2f", height];
return cell;
}

其中的heightForRowAtIndex:方法是一个工具类方法,它可以根据不同的索引行返回高度。

1
2
3
4
5
6
7
8
- (CGFloat)heightForRowAtIndex:(NSUinteger)index {
CGFloat result;
for (NSInteger i = 0 i < le5; i++) {
result = sqrt((double)i);
}
result = (index % 3 + 1) * 20.0;
return result;
}

如果我们有一个复杂的表格,它其中的单元格具有不同的高度,所以我们需要构建单元格,让它能够自己决定自身的高度,将会要花费很长的时间。为了能够模拟这个,我们将会花费一个巨大的循环来计算-其实这并没有什么实际用途,我们只是让它来进行一些消耗时间而已。

我们同样需要一个代理来返回行高,所以,我们创建一个SCNonEstimatingTableViewDelegate:

1
2
3
@interface SCNonEstimatingTableViewDelegate : NSObject <UITableViewDelegate>
- (instancetype)initWithHeightBlock:(CGFloat (^)(NSUinteger index))heightBlock;
@end

这个方法里面有一个块参数,用来计算具体的行高的:

1
2
3
4
5
6
7
8
9
10
11
12
13
@implementation SCNonEstimatingTableViewDelegate {
CGFloat (^_heightBlock)(NSUInteger index);
}

- (instancetype)initWithHeightBlock:(CGFloat (^)(NSUinteger))heightBlock {
self = [super init];
if (self) {
_heightBlock = [heightBlock copy];
}
return self;
}

@end

然后我们来实现代码相关的方法:

1
2
3
4
5
#pragma mark - UITableViewDelegate methods
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(@"Height (row %d)", indexPath.row);
return _heightBlock(indexPath.row);
}

在这里将会调用一个log日志,然后使用block来计算出行高,我们在view controllr中进行另外的一些连接:

1
2
3
4
5
6
7
8
- (void)viewDidLoad {
[super viewDidLoad];

_delegate = [[SCNonEstimatingTableViewDelgate alloc] initWithHeightBlock:^CGFloat(NSUInteger index) {
return [self heightForRowAtIndex:index];
}];
self.tableView.delegate = _delegate;
}

运行应用程序然后就可以展示可变的行高的表格:

查看打印的log日志,我们会发现在我们第一次渲染表格的时候会对每一个单元格调用获得高度的方法。这是因为表格视图需要知道它自身的全部高度。我们的heightForRowAtIndex:工具方法模拟了复杂的长时间计算过程。添加了逻辑上面的时间,我们就会在加载表格视图的时候有一些延迟,日志信息如下:

With estimation

新的估算高度的代理方法提供一种提高初始化延迟来进行渲染表格的方法。如果除了tableView:heightForRowAtIndexPath:之外,我们实现tableView:estimatedHeightForRowAtIndexPath:,然后在渲染表格视图之前不是调用 height 方法,而是每一行数据调用 estimateHeight 方法。height 方法仅仅用来为已经渲染到屏幕上面的行调用。因此,我们将高度计算划分为一个方法需要准确的高度(因为单元格即将出现在屏幕),和一个方法只是用来计算整个tableview的高度(因此不需要完全准确)。

为了验证这一点,我们需要创建一个新的代理,它将会实现估算高度的方法:

1
2
3
@interface SCEstimatingTableViewDelgate: SCNonEstimatingTableViewDeletate 
- (instancetype)initWithHeightBlock(CGFloat (^)(NSUinteger index))heightBlock estimationBlock:(CGFloat (^)(NSUInteger index))estimationBlock;
@end

在这里,我们得到有两个block的构造器,一个将会用来得到准备的高度,另一个用来估算:

1
2
3
4
5
6
7
8
9
10
11
12
@implentation SCEstimatingTableViewDelegate {
CGFloat (^_estimationBlock)(NSUinteger index);
}

- (instancetype)initWithHeightBlock(CGFloat (^)(NSUInteger index))heightBlock estimationBlock:(CGFloat (^)(NSUInteger index))estimationBlock {
self = [super initWithHeightBlock:heightBlock];
if (self) {
_estimationBlock = [estimationBlock copy];
}
return self;
}
@end

然后我们来实现这个新的估算方法:

1
2
3
4
5
#pragma mark - UITableViewDelegate methods
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(@"Estimating height (row %d)", indexPath.row);
return _estimationBlock(indexPath.row);
}

在view controller中更新一些简单的估算高度的方法,只需要返回我们单元格的平均高度(40.0);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- (void)viewDidLoad {
[super viewDidLoad];

if (self.enableEstimation) {
_delegate = [[SCEstimatingTableViewDelegate alloc] initWithHeightBlock:^CGFloat(NSUInteger index) {
return [self heightForRowAtIndex:index];
} estimationBlock:^CGFloat(NSUInteger index) {
return 40.0;
}];
}else {
_delegate = [[SCNonEstimatingTableViewDelegate alloc] initWithHeightBlock:^CGFloat(NSUInteger index) {
return [self heightForRowAtIndex:index];
}];
}
self.tableView.delegate = _delegate;
}

运行应用程序,然后观察日志信息然后我们就可以看到height方法在初始化渲染之前没有被调用,但是调用的是estimated height方法。在单元格已经被渲染了之后才会调用真实的height方法。因此,我们的加载时间比以前减少了不少:

Conclusion - 总结

如前所述,这个例子有点做作,但它确实证明相当好,如果计算的实际身高是努力工作实现新的估计高度方法真的可以提高应用程序的响应能力,特别是如果你有一个大tableview。有额外的高度估计方法一节页眉和页脚,在完全相同的方式工作。它可能不是一个开创性的API改变,但在某些情况下,能真正提高用户体验,所以它绝对是值得做的事情。

本文翻译自:iOS7 Day-by-Day :: Day 19 :: UITableView Row Height Estimation

文章目录
  1. 1. Introduction - 介绍
  2. 2. Without estimation - 没有估算
  3. 3. With estimation
  4. 4. Conclusion - 总结
,