这篇文章主要介绍如何在开发中灵活的计算具有动态高度内容的Cell的高度。
关于计算UITableViewCell的高度(Cell中有Lable、TextView等动态内容的控件),大体的实现思路无非是两种。一种就是以前传统的绝对布局方法(Frame),涉及到的API sizeToFit、sizeThatFits: ,通过数据内容挨个反算Cell中控件的Frame,然后计算出Cell的总高度,总体上来看就像这样:cellHeight = labelHeight+imageHeight+subViewHeight+... 。第二种方法就比较灵活省事,通过AutoLayout来动态算高,我们只需要设置好Cell中的控件约束,再调用系统提供的API:systemLayoutSizeFittingSize: 算出Cell高度即可,基本不需要关心和计算每个控件的具体高度。当然这种方法要求对AutoLayout要非常熟悉,因为约束如果设置错误或者不当就会导致计算的高度为0,计算完全错误。我们就来讲讲使用AutoLayout计算Cell高度的方法吧,因为这是目前开发比较高效的方法也是需要掌握的技巧。
首先我们要学会使用AutoLayout在Cell上面正确的设置约束,因为这是成功的第一步。如果约束设置错误或者不当都会导致计算的高度为0。本文只用两个简单且典型的Cell样式作例子:一种是有Label的Cell,另一种是有TextView的Cell。
上面的Cell由一个图片和一个Label组成。其中图片宽高固定,顶部离Cell的顶部5像素,横向和Cell居中对齐。Label左右距离Cell边缘均为16像素,顶部离图片5像素,底部离Cell底部10像素。这里需要注意的是,由于Lable是具有动态高度内容的控件,所以它要求Cell的高度要随着Lable的内容变化。这一步我们这里要使用AutoLayout来实现。我们需要分析一下,影响Lable自身高度的因素无非就是Lable的自身宽度,因为宽度影响Lable的换行,越长换行越少,高度也就越小。所以总的来说有三个影响因素:
1、一个是Lable自身的 numberOfLines 属性(控制Lable内容是否可以换行),当numberOfLines=0 时代表Lable的行数不定,也就是高度可以动态变化。所以如果Lable的内容是动态的numberOfLines 一定要设置为0,否则高度计算是错误的。
2、另外一个就是 preferredMaxLayoutWidth 属性(控制Lable内容的最大宽度)。长度越长换行越少,这个大家应该都明白。要Lable准确计算自身高度,一定要设置preferredMaxLayoutWidth ,毕竟它必须知道自己的最大宽度才能知道到哪里需要换行,而换行直接影响高度。
3、最后有个问题需要注意,Lable的自身计算高度的因素都设置好了,我们在设置约束的时候还需要让Cell直接知道Lable的高度。而这个就需要我们在完善了Lable自身的约束的时候,要保证Lable纵向(上下边缘)要有约束支撑。毕竟要让Lable的父视图或者平行子view知道Lable的高度,换句话来说就是Lable的内容要把布局的空间撑开(这里的实现是Lable的上边缘设置了离图片底部5个像素的距离,Lable的底部边缘离Cell的ContentView底部10个像素的距离)。 正确设置完上面所说的约束后,我们只需要调用 systemLayoutSizeFittingSize: 方法就可以计算View的高度了。UIView在IOS6推出AutoLayout的时候就开始提供了systemLayoutSizeFittingSize: 这个API方便我们准确的计算视图的大小。接下来我们动态计算Cell的高度就会变得很简单了:CGFloat cellHeight = [cell.contentViewsystemLayoutSizeFittingSize:UILayoutFittingCompressedSize]+1 。很多人要问为什么要加1呢?这是由于是在 cell.contentView 上调用这个方法,那么返回的值是contentView的高度,而UITableViewCell的高度要比它的contentView要高1,也就是要加上它的分隔线的高度。
这里在Cell中的实现如下:(将计算Cell高度的方法在它自身中去实现)
- (CGFloat)getCellHeigh{
CGFloat cellHeight = 0.0f;
//contentView的内容高度比cell的高度少1厘米,这里可以去IB上看看
cellHeight += [self.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height+1;
return cellHeight;
}
上面的Cell由一个图片和一个TextView组成。其中图片宽高固定,纵向和Cell居中对齐。TextView的左边缘距离图片16像素,左边缘距离Cell16像素,顶部和底部都离 Cell 30像素(TextView的高度随着Cell的高度变化)。
由于TextView和Label一样也是具有动态高度内容的控件,所以它也要求Cell的高度要随着Lable的内容变化。但是有一点需要初学者注意的:在AutoLayout下,view中就算包含了一个TextView,并且TextView的约束都是正确的,但是等我们调用systemLayoutSizeFittingSize:这个方法的时候,却发现计算出来的高度和我们预期的不一样,并且这个高度刚刚忽略了TextView自身的高度!所以,如果Cell中包括了TextView,我们就要回到Frame计算高度的时代,使用 sizeToFit、sizeThatFits:单独计算出TextView的高度,然后再加上这个高度!
到这里需要讲讲 sizeToFit、sizeThatFits:这两个API的基本用法。其实看一下官方的文档就知道,sizeThatFits:方法就是根据传入的Size,计算并返回最适合View内容大小的Size,这个方法不改变当前View的坐标大小,只负责计算。而sizeToFit方法不需要传入一个指定的Size,而是根据View目前自身的坐标大小计算出最适合View内容大小的Size并且将当前View的大小修改为这个最合适的值。所以到这里,我们要计算TextView的高度,就要调用sizeThatFits:方法传入一个我们指定的Size去计算TextView的最适合大小。这里在Cell中的实现如下:
- (CGFloat)getCellHeigh{
CGFloat cellHeight = 0.0f;
cellHeight += [self.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
cellHeight += [contentTextView sizeThatFits:CGSizeMake([UIScreen mainScreen].bounds.size.width-106-16, FLT_MAX)].height;
if(cellHeight<=80){
cellHeight = 80+5+5;
}
cellHeight += 1;
return cellHeight;
}
本篇文章关于动态计算UITableViewCell高度的Demo已经上传到Github,感兴趣的可以看一下。DynamicCalculateCellHeightDemo
可以确定的是我们一般需求情况下TableView的样式绝对是五花八门的。绝对不是一个两个的量级。所以为了不必要的Cell实例的创建,我们的策略必须选对。其实我们可以为每种样式的Cell只创建一个实例,这个实例不参与界面的展示,也就是不会显示在TableView上,只是用来计算Cel的高度。然后把这些负责计算高度的实例保存在一个全局的字典里,并且通过Cell的重用标记 Identifier作为Key一一对应起来。这样我们就只需要维护一个Cell实例和 Identifier 一一对应的字典,计算同一种样式的Cell的高度的时候只需要根据 Identifier 到这个全局字典去获取已经创建好的实例,然后用这个实例去计算就可以了,避免了重复创建的窘境。
#import "UITableView+FDTemplateLayoutCell.h"
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return [tableView fd_heightForCellWithIdentifier:@"reuse identifer" configuration:^(id cell) {
// Configure this cell with data, same as what you've done in "-tableView:cellForRowAtIndexPath:"
// Like:
// cell.entity = self.feedEntities[indexPath.row];
}];
}