本文转载自:https://www.mgenware.com/blog/?p=507
首先需要在Storyboard中创建好MyTableViewController
,使用动态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(设置label的preferredMaxLayoutWidth后可以正常显示多行label)
cell.titleLabel.text = [_dataSource objectAtIndex:indexPath.row];
}
接下来是关键的
heightForRowAtIndexPath
方法,这里的逻辑上面已经讲过,不需要用Cell重用机制,我们只创建一个Cell,利用这个Cell,不停地加载内容,然后返回高度就可以了,这两个步骤的辅助方法上面也都有,我们直接用,如下代码:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
//只创建一个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
{
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
{
return [super initWithCoder:coder];
}