您的位置:首页 > 其它

动态计算cell高度(1)

2015-08-19 15:55 411 查看
原文地址:http://www.mgenware.com/blog/?p=507

首先需要在Storyboard中创建好
TableViewController
,使用动态Cell,在Prototype
Cells中设计好Cell界面。





接着,定义好Autolayout,注意Autolayout一定要在上下都绑定控件的位置,不要只从上到下定义,只有正确定义Autolayout,后面我们用到的
systemLayoutSizeFittingSize
方法才会返回正确的结果。

如下图:





Xcode会提示Autolayout的各种Ambiguity,提示修改控件的Compression resistance,如下图:





这里让Xcode智能修正就可以了,具体哪个控件的Compression resistance无所谓,因为我们最终的目的是让
UITabelViewCell
的高度去适合所有控件的大小。

然后,因为是在Xcode 5 iOS 7模式下设计的Storyboard,所以在iOS 7下运行肯定是没问题的:





接着在iOS 6上运行:





什么情况?出现这个问题的原因是:iOS 7和iOS 6中的许多控件默认高度都是不一样的,在其他普通UIView下,有了Autolayout,控件当然会正确显示。但是
UITableViewCell
的高度是通过
UITableView
heightForRowAtIndexPath
方法来返回的。默认情况下,它是保持不变的。所以当Cell内控件的高度发生变化后,如果Cell高度没有因此而作出调整,肯定会出问题的。

来慢慢看问题,首先,如何测量使用Autolayout的
UIView
的尺寸?可以使用
UIView
systemLayoutSizeFittingSize
方法,对于
UITableViewCell
,那就是测量其
contentView
的大小。那么,本例中需要返回Autolayout的Cell的高度,则可以写一个辅助方法,这样:

- (CGFloat)getCellHeight:(UITableViewCell*)cell
{
[cell layoutIfNeeded];
[cell updateConstraintsIfNeeded];

CGFloat height = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
return height;
}


接着,另一个问题来了,是关于
UITableView
的Cell重用机制。我们必须在
UITableView
heightForRowAtIndexPath
方法中返回根据Cell内容计算出来的动态高度,对于使用Autolayout的Cell,必须创建这个Cell才可以获取他的动态高度。而经测试发现,如果在
heightForRowAtIndexPath
调用Cell重用方法,也就是
dequeueReusableCellWithIdentifier
方法,Cell还是会被重复创建,也就是说Cell的重用机制在
heightForRowAtIndexPath
中是无效的。解决方案是,在
heightForRowAtIndexPath
只创建一个Cell,这个Cell专门用作测量所有Cell的高度,然后在
cellForRowAtIndexPath
继续使用Cell重用逻辑就可以。

我们来再看本例代码,首先在
TableViewController
中加入必要的字段,声明数据源,加入测试数据等,这些都是很简单的内容,不需要多讲:

//测试数据源
NSMutableArray *_dataSource;


viewDidLoad
中初始化相关数据:

//viewDidLoad 初始化
_dataSource = [NSMutableArray arrayWithArray:@[@"Mgen", @"Tony", @"Jerry", @"一二三"]];


然后把Cell加载数据的逻辑写在一个方法里,这个方法是被
heightForRowAtIndexPath
cellForRowAtIndexPath
方法所共用的,因为不管是测量Cell的高度还是展示Cell,我们都需要Cell加载相应的数据:

- (void)loadCellContent:(MyCell*)cell indexPath:(NSIndexPath*)indexPath
{
//这里把数据设置给Cell
cell.titleLabel.text = [_dataSource objectAtIndex:indexPath.row];
}


接下来是关键的
heightForRowAtIndexPath
方法,这里的逻辑上面已经讲过,不需要用Cell重用机制,我们只创建一个Cell,利用这个Cell,不停地加载内容,然后返回高度就可以了,这两个步骤的辅助方法上面也都有,我们直接用,如下代码:

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
//log
[[GlobalCounter getInstance] add:@"get height"];

//只创建一个cell用作测量高度
static MyCell *cell = nil;

if (!cell)
cell = [self.tableView dequeueReusableCellWithIdentifier:@"MyCell"];

[self loadCellContent:cell indexPath:indexPath];
return [self getCellHeight:cell];
}


然后是
cellForRowAtIndexPath
方法,这里调用
dequeueReusableCellWithIdentifier
进行Cell重用就然后加载Cell内容就可以了:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//log
[[GlobalCounter getInstance] add:@"get cell"];

static NSString *CellIdentifier = @"MyCell";
//注意在heightForRowAtIndexPath:indexPath无法使用dequeueReusableCellWithIdentifier:forIndexPath:
MyCell *cell = [self.tableView dequeueReusableCellWithIdentifier:CellIdentifier];
//用dequeueReusableCellWithIdentifier:就得判断Cell为nil的情况
//如果在Storyboard中Prototype Cells中设置了具体Table View Cell的Identifier也是"MyCell"(也就是重用ID),那这里不会有返回nil的情况
if (!cell)
{
cell = [[MyCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}

[self loadCellContent:cell indexPath:indexPath];
return cell;
}


读者可以看到,上面代码里,我在
heightForRowAtIndexPath
cellForRowAtIndexPath
方法里都打了Log,还有一处需要打Log的地方,就是Cell本身的创建上,注意Storyboard中
UITableViewCell
的创建是在
initWithCoder
方法中的,而不是
initWithStyle:reuseIdentifier
方法里的。

- (instancetype)initWithCoder:(NSCoder *)coder
{
[[GlobalCounter getInstance] add:@"create cell"];
return [super initWithCoder:coder];
}


OK,现在再次运行程序,即便是你Storyboard中把Cell高度手动调整成这样:





在iOS 6下会显示出正确的结果:





iOS 7下也下一样:





最后Log的信息,在1000个数据源的情况下,运行程序后没有进行任何滚动操作:

"create cell" = 7;    //创建Cell 7次
"get cell" = 6;       //调用heightForRowAtIndexPath 6次
"get height" = 2006;  //调用cellForRowAtIndexPath 2006次


源代码下载

mgen_tableViewCellHeight.zip

源代码环境:Xcode 6.0
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: