动态计算cell高度(1)

原文地址:http://www.mgenware.com/blog/?p=507

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

屏幕快照 2013-12-23 下午5.44.56

 

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

如下图:

屏幕快照 2013-12-23 下午5.47.41

 

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

屏幕快照 2013-12-23 下午5.45.07

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

 

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

屏幕快照 2013-12-23 下午5.47.57

接着在iOS 6上运行:

屏幕快照 2013-12-23 下午5.48.30

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

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

- (CGFloat)getCellHeight:(UITableViewCell*)cell
{
    [cell layoutIfNeeded];
    [cell updateConstraintsIfNeeded];
    
    CGFloat height = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
    return height;
}

 

接着,另一个问题来了,是关于UITableView的Cell重用机制。我们必须在UITableViewheightForRowAtIndexPath方法中返回根据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加载数据的逻辑写在一个方法里,这个方法是被heightForRowAtIndexPathcellForRowAtIndexPath方法所共用的,因为不管是测量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:就得判断Cellnil的情况
    //如果在StoryboardPrototype Cells中设置了具体Table View CellIdentifier也是"MyCell"(也就是重用ID),那这里不会有返回nil的情况
    if (!cell)
    {
        cell = [[MyCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
    }
    
    [self loadCellContent:cell indexPath:indexPath];
    return cell;
}

 

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

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

 

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

屏幕快照 2013-12-23 下午6.11.16

 

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

屏幕快照 2013-12-23 下午6.01.50

iOS 7下也下一样:

屏幕快照 2013-12-23 下午5.47.57

 

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

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

 

源代码下载 
mgen_tableViewCellHeight.zip 
源代码环境:Xcode 6.0



  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值