iOS7 UITableView Row Height Estimation
This post is part of a daily series of posts introducing the most exciting new parts of iOS7 for developers -#iOS7DayByDay. To see the posts you’ve missed check out the introduction page, but have a read through the rest of this post first!
Introduction
Today we’re going to take a look at a fairly small addition to the UIKit API, but one which could make quite a difference to the user experience of apps with complex table views. Row height estimation takes the form of an additional method on the table view delegate, which, rather than having to return the exact height of every row at initial load, allows an estimated size to be returned instead. We’ll look at why this is an advantage in today’s post. In order to demonstrate its potential we’ll construct a slightly contrived app which has a table view which we can view both with and without row height estimation.
The code for this blog post is available in the github repo which accompanies this series – at github.com/ShinobiControls/iOS7-day-by-day.
Without estimation
We create a simple UITableView with a UITableViewController, containing just 1 section with 200 rows. The cells contain their index and their height, which varies on a row-by-row basis. This is important – if all the rows are the same height then we don’t need to implement the heightForRowAtIndexPath: method on the delegate, and we won’t get any improvement out of using the new row height estimation method.
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return 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;
}
The heightForRowAtIndex: method is a utility method which will return the height of a given row:
- (CGFloat)heightForRowAtIndex:(NSUInteger)index
{
CGFloat result;
for (NSInteger i=0; i < 1e5; i++) {
result = sqrt((double)i);
}
result = (index % 3 + 1) * 20.0;
return result;
}
If we had a complex table with cells of differing heights, it is likely that we would have to construct the cell to be able to determine its height, which takes a long time. To simulate this we’ve put a superfluous loop calculation in the height calculation method – it isn’t of any use, but takes some computational time.
We also need a delegate to return the row heights as we go, so we create SCNonEstimatingTableViewDelegate:
@interface SCNonEstimatingTableViewDelegate : NSObject <UITableViewDelegate>
- (instancetype)initWithHeightBlock:(CGFloat (^)(NSUInteger index))heightBlock;
@end
This has a constructor which takes a block which is used to calculate the row height of a given row:
@implementation SCNonEstimatingTableViewDelegate
{
CGFloat (^_heightBlock)(NSUInteger index);
}
- (instancetype)initWithHeightBlock:(CGFloat (^)(NSUInteger))heightBlock
{
self = [super init];
if(self) {
_heightBlock = [heightBlock copy];
}
return self;
}
@end
And we implement the relevant delegate method:
#pragma mark - UITableViewDelegate methods
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(@"Height (row %d)", indexPath.row);
return _heightBlock(indexPath.row);
}
This logs that it has been called and uses the block to calculate the row height for the specified index path. With a bit of wiring up in the view controller then we’re done:
- (void)viewDidLoad
{
[super viewDidLoad];
_delegate = [[SCNonEstimatingTableViewDelegate alloc] initWithHeightBlock:^CGFloat(NSUInteger index) {
return [self heightForRowAtIndex:index];
}];
self.tableView.delegate = _delegate;
}
Running the app up now will demonstrate the variable row height table:
![]()
Looking at the log messages we can see that the row height method gets called for every single row in the table before we first render the table. This is because the table view needs to know its total height (for drawing the scroll bar etc). This can present a problem in complex table views, where calculating the height of a row is a complex operation – it might involve fetching the content, or rendering the cell to discover how much space is required. It’s not always an easy operation. Our heightForRowAtIndex: utility method simulates this complexity with a long loop of calculations. Adding a bit of timing logic we can see that in this contrived example (and running on a simulator) we have a delay of nearly half a second from loading the tableview, to it appearing:
![]()
With estimation
The new height estimation delegate methods provide a way to improve this initial delay to rendering the table. If we implementtableView:estimatedHeightForRowAtIndexPath: in addition to tableView:heightForRowAtIndexPath: then rather than calling theheight method for every row before rendering the tableview, the estimatedHeight method will be called for every row, and theheight method just for rows which are being rendered on the screen. Therefore, we have separated the height calculation into a method which requires the exact height (since the cell is about to appear on screen), and a method which is just used to calculate the height of the entire tableview (hence doesn’t need to be perfectly accurate).
To demonstrate this in action we create a new delegate which will implement the height estimation method:
@interface SCEstimatingTableViewDelegate : SCNonEstimatingTableViewDelegate
- (instancetype)initWithHeightBlock:(CGFloat (^)(NSUInteger index))heightBlock
estimationBlock:(CGFloat (^)(NSUInteger index))estimationBlock;
@end
Here we’ve got a constructor with 2 blocks, one will be used for the exact height method, and one for the estimation:
@implementation 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
And then we implement the new estimation method:
#pragma mark - UITableViewDelegate methods
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(@"Estimating height (row %d)", indexPath.row);
return _estimationBlock(indexPath.row);
}
Updating the view controller with a much cheaper height estimation method – just returning the average height for our cells (40.0).
- (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;
}
Running the app up now and observing the log and we’ll see that the height method no longer gets called for every cell before initial render, but instead the estimated height method. The height method is called just for the cells which are being rendered on the screen. Consequently see that the load time has dropped to a fifth of a second:
![]()
Conclusion
As was mentioned before, this example is a little contrived, but it does demonstrate rather well that if calculating the actual height is hard work then implementing the new estimation height method can really improve the responsiveness of your app, particularly if you have a large tableview. There are additional height estimation methods for section headers and footers which work in precisely the same manner. It might not be a groundbreaking API change, but in some cases it can really improve the user experience, so it’s definitely worth doing.
Don’t forget that you can get the code for this project on github at github.com/ShinobiControls/iOS7-day-by-day. If you have any feedback/comments then feel free to use the comments box below, or hit me up on twitter – @iwantmyrealname.
sam
iOS7 UITableView Row Height Estimation的更多相关文章
- wpf datagrid row height 行高自动计算使每行行高自适应文本
wpf 的datagrid的行高 要么是Auto,要么是定值:但会带来麻烦就是每行行高都一样. 当需要按内容(主要是wrap 换行的textbox或textblock)来动态调整行高的时候,需要用到d ...
- ios 报错 Invalid row height provided by table delegate. Value must be at least 0.0, or UITableViewAutomaticDi......
Invalid row height provided by table delegate. Value must be at least 0.0, or UITableViewAutomaticDi ...
- IOS7 UITableView一行滑动删除后 被删除行的下一行的点击事件将被忽略解决办法
- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSI ...
- ios7 UITableView 分割线在 使用selectedBackgroundView 选中时有些不显示
UITableView 选中cell ,默认会有一个灰色的背景遮罩效果,这个灰色遮罩就是cell 自带的 selectedBackgroundView 我们可以设置selectedBackgroun ...
- ios7 UITableView底线右移
在ios7上UITableView底线右移了,我们可以通过添加代码来让它铺满整个屏幕的宽,在使用前要加上判断是否有这个方法 if ([_tableView respondsToSelector:@se ...
- 适配iOS7之—UITableView和UISearchBar
iOS7中,如果用UITableViewStyleGrouped的话,里面的cell会比原来的拉长了,这样做应该是为了统一和UITableViewStylePlain风格时cell的大小一致,所以改用 ...
- UITableView优化那点事
forkingdog关于UITableView优化的框架其实已经能够应用在一般的场景,且有蛮多的知识点供我们借鉴,借此站在巨人的肩膀上来分析一把. 至于UITableView的瓶颈在哪里,我相信网上随 ...
- [转]ng-grid Auto / Dynamic Height
本文转自:https://stackoverflow.com/questions/23396398/ng-grid-auto-dynamic-height I think I solved this ...
- UITableViewCell 高度计算从混沌初始到天地交泰
[原创]UITableViewCell 高度计算从混沌初始到天地交泰 本文主要基予iOS UITableViewCell 高度自适应计算问题展开陈述,废话少说直入正题: UITableView控件可能 ...
随机推荐
- Java HotSpot(TM) 64-Bit Server VM warning: Insufficient space for shared memory file...
Java HotSpot(TM) 64-Bit Server VM warning: Insufficient space for shared memory file: 把tomcat中的日志删除, ...
- Nginx+Tomcat搭建负载均衡集群
Nginx ("engine x") 是一个高性能的HTTP和反向代理服务器, 使用 Nginx 可以使得程序在高并发的情况下依旧可以保持良好的性能.使用 Nginx+Tomcat ...
- RobotFramework教程使用笔记——时间控件的相关操作
在web测试过程中,我们可能会遇到时间控件,有的是支持直接输入的,有的为了保证输入时间格式的一致性是只支持点击选择的,那么这个时候如何用robotframework来操作呢? 看下面这个例子: 这个是 ...
- 树莓派保持网络连接shell脚本
源:http://www.jianshu.com/p/96aa26d165c3 (我的简书文章) 故事背景 树莓派3b现在有了板载无线网卡,可以很方便的连接到wifi.但是无线路由器不能保证永远正常工 ...
- C++中map容器的说明和使用技巧
C++中map容器提供一个键值对容器,map与multimap差别仅仅在于multiple允许一个键对应多个值. 一.map的说明 1 头文件 #include <map> 2 定义 ma ...
- 进程优先级、nice值
进程cpu资源分配就是指进程的优先权(priority).优先权高的进程有优先执行权利.配置进程优先权对多任务环境的linux很有用,可以改善系统性能.还可以把进程运行到指定的CPU上,这样一来,把不 ...
- python操作oracle数据库
本文主要介绍python对oracle数据库的操作学习 包含:oracle数据库在Windows操作系统下的安装和配置.python需要安装的第三方拓展包以及基本操作的样例学习. 1 ...
- C语言system()函数:执行shell命令
头文件:#include <stdlib.h> 定义函数:int system(const char * string); 函数说明:system()会调用fork()产生子进程, 由子进 ...
- kali-linux简单学习(二)
一.SET 社会工程学工具包有一个叫devolution. 启动 setoolkit 里面可以进行一些钓鱼攻击. tabnabbing attack这种方式是完整克隆一个网站挂到SET创建的web服 ...
- UNP总结 Chapter 12~14 IPv4与IPv6的互操作性、守护进程和inet超级服务器、高级I/O函数
一.IPv4与IPv6的互操作性 1.IPv4客户与IPv6服务器 拥有双重协议栈的主机的一个基本特性就是:其上运行的IPv6服务器既能应付IPv4客户,又能应付IPv6客户.这是通过使用IPv4映射 ...