动态计算UITableViewCell的高度

        这篇文章主要介绍如何在开发中灵活的计算具有动态高度内容的Cell的高度。

        关于计算UITableViewCell的高度(Cell中有Lable、TextView等动态内容的控件),大体的实现思路无非是两种。一种就是以前传统的绝对布局方法(Frame),涉及到的API  sizeToFitsizeThatFits: ,通过数据内容挨个反算Cell中控件的Frame,然后计算出Cell的总高度,总体上来看就像这样:cellHeight = labelHeight+imageHeight+subViewHeight+... 第二种方法就比较灵活省事,通过AutoLayout来动态算高,我们只需要设置好Cell中的控件约束,再调用系统提供的API:systemLayoutSizeFittingSize:  算出Cell高度即可,基本不需要关心和计算每个控件的具体高度。当然这种方法要求对AutoLayout要非常熟悉,因为约束如果设置错误或者不当就会导致计算的高度为0,计算完全错误。我们就来讲讲使用AutoLayout计算Cell高度的方法吧,因为这是目前开发比较高效的方法也是需要掌握的技巧。


一:在Cell上正确设置AutoLayout

        首先我们要学会使用AutoLayout在Cell上面正确的设置约束,因为这是成功的第一步。如果约束设置错误或者不当都会导致计算的高度为0。本文只用两个简单且典型的Cell样式作例子:一种是有Label的Cell,另一种是有TextView的Cell。


1、有Label的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;
}


2、有TextView的Cell



        上面的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


二:动态计算Cell高度的整体思路

        上面一节我们知道了如何在Cell上通过设置AutoLayout来动态计算高度,这个是最基本。接下来我们如果要动态的计算TableView中各个样式的Cell的高度,又该如何入手?
        首先对于TableView的工作方式我们做一个初步的了解和总结。TableView是一次性将所有Cell的高度计算出来的,也就说如果有1万个Cell,那么代理方法 - (CGFloat)tableView:(UITableView )tableView heightForRowAtIndexPath:(NSIndexPath )indexPath 就会触发1万次,TableView必须先计算出内容的总高度,然后才显示内容。所以我们计算Cell高度的处理基本都在 heightForRowAtIndexPath: 这个方法里。
根据上面我们的处理,我们要计算一个Cell的高度,就必须先获得一个Cell的实例,然后调用它的 getCellHeigh 方法获得高度。那么问题来了,如果有1万个Cell那么我们就要要在 heightForRowAtIndexPath: 方法里获取一个1万个Cell实例,然后一个一个去计算。首先,这样做内存的浪费占用太严重,肯定不行。 实际上在 heightForRowAtIndexPath: 方法里调用方法[tableView dequeueReusableCellWithIdentifier:@"CellIdentifier"]  不会从重用队列里面去获取已经创建的重用Cell实例,而是重新创建一个新的)。

        可以确定的是我们一般需求情况下TableView的样式绝对是五花八门的。绝对不是一个两个的量级。所以为了不必要的Cell实例的创建,我们的策略必须选对。其实我们可以为每种样式的Cell只创建一个实例,这个实例不参与界面的展示,也就是不会显示在TableView上,只是用来计算Cel的高度。然后把这些负责计算高度的实例保存在一个全局的字典里,并且通过Cell的重用标记 Identifier作为Key一一对应起来。这样我们就只需要维护一个Cell实例和 Identifier 一一对应的字典,计算同一种样式的Cell的高度的时候只需要根据 Identifier 到这个全局字典去获取已经创建好的实例,然后用这个实例去计算就可以了,避免了重复创建的窘境。



三:关于 UITableView+FDTemplateLayoutCell

        UITableView+FDTemplateLayoutCell 是一个比较流行和好用的第三方开源库,宗旨就是优化UITableViewCell的高度计算和UITableView的滑动优化。用它的话来说就是可以用一句话解决高度问题,就是这么简单!那它到底做了什么事呢?可以让我们如此简单的动态计算出UITableViewCell的高度呢?
其实,这个开源库计算Cell高度的思路和处理方法和文章上面说的大体上是一致的:通过维护一个UITableView的扩展,UITableView绑定一个全局字典,这个字典将同一类型的Cell实例作为value、Identifier作为key 一一对应起来。计算同一种样式的Cell的高度的时候只需要根据 Identifier 到这个全局字典去获取已经创建好的实例,然后用这个实例去计算就可以了。通过封装后,我们真的可以通过一句话就可以计算UITableViewCell的高度了!(当然这个库还提供了强大的高度预缓存功能)

#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];
    }];
}

        通过阅读源码,发现这个库有一个很高明和简明的处理细节!这个问题也是我们文章前面提到的:UILabel 需要多行显示的时候,也就是当UILabel 行数大于0时,需要指定 preferredMaxLayoutWidth后它才知道自己什么时候该换行。这是个“鸡生蛋蛋生鸡”的问题,因为 UILabel 需要知道 superview 的宽度才能折行,而 superview 的宽度还依仗着子 view 宽度的累加才能确定。我们上面的做法是给UILabel的preferredMaxLayoutWidth指定一个确定的值。而这个开源库的精明做是给Cell的contentView加了一个定宽的约束,强制让contentView的子view知道父view的宽度,解决了需要手动指定preferredMaxLayoutWidth的问题。


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值