Auto Layout 使用心得(五)—— 根据文字、图片自动计算 UITableViewCell 高度

2015-4-19   /   阅读数:57331   /   分类: iOS & Swift

此系列文章代码仓库在 https://github.com/johnlui/AutoLayout ,有不明白的地方可以参考我的 Auto Layout 设置哦,下载到本地打开就可以了。

简介

本文中,我们将一起使用 Auto Layout 技术,让 UITableViewCell 的高度随其内部的 UILabel 和 UIImageView 的内容自动变化。

搭建界面

恢复之前删除的按钮

放置一个按钮,恢复到 firstTableViewController 的连接:

Image

别忘了添加约束让他居中哦。

修改 firstTableViewCell

将 firstTableViewCell 的尺寸设置为 600 * 81,将 logo 的尺寸设置为 80 * 80。将 logo 的约束修改为如下图所示:

Image

修改 label 的尺寸和位置,添加约束如下图:

Image

给 ViewController 增加 UINavigationController 嵌套

为了便于返回。操作如下图:

Image

查看结果

Image

根据 label 自动计算 firstTableViewCell 高度

选中 label,设置 lines 行数为 0,表示不限长度自动折行:

Image

修改 label 的文字内容让其超出一行:

import UIKit

class firstTableViewController: UITableViewController {
    
    var labelArray = Array<String>() // 用于存储 label 文字内容

    override func viewDidLoad() {
        super.viewDidLoad()

        var nib = UINib(nibName: "firstTableViewCell", bundle: nil)
        self.tableView.registerNib(nib, forCellReuseIdentifier: "firstTableViewCell")
        
        // 循环生成 label 文字内容
        for i in 1...10 {
            var text = ""
            for j in 1...i {
                text += "Auto Layout"
            }
            labelArray.append(text)
        }
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }

    // MARK: - Table view data source

    override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
        return 50
    }
    override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        return 1
    }

    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return labelArray.count
    }

    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCellWithIdentifier("firstTableViewCell", forIndexPath: indexPath) as! firstTableViewCell

        cell.firstLabel.text = labelArray[indexPath.row]

        return cell
    }
}

现在到了最关键的时刻,驱动 UITableViewCell 适应 Label 内容:

1. 使用 estimatedHeightForRowAtIndexPath 替代 heightForRowAtIndexPath

estimatedHeightForRowAtIndexPath 是 iOS 7 推出的新 API。如果列表行数有一万行,那么 heightForRowAtIndexPath 就会在列表显示之前计算一万次,而 estimatedHeightForRowAtIndexPath 只会计算当前屏幕中显示着的几行,会大大提高数据量很大时候的性能。

2. 新建一个 prototypeCell 成员变量以复用,并在 viewDidLoad 中初始化

class firstTableViewController: UITableViewController {
    
    var labelArray = Array<String>() // 用于存储 label 文字内容
    
    var prototypeCell: firstTableViewCell!

    override func viewDidLoad() {
        super.viewDidLoad()

        var nib = UINib(nibName: "firstTableViewCell", bundle: nil)
        self.tableView.registerNib(nib, forCellReuseIdentifier: "firstTableViewCell")
        
        // 初始化 prototypeCell 以便复用
        prototypeCell = tableView.dequeueReusableCellWithIdentifier("firstTableViewCell") as! firstTableViewCell
        
......

3. 计算出高度

override func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
    let cell = prototypeCell
    cell.firstLabel.text = labelArray[indexPath.row]
    return cell.contentView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height + 1
}

4. 查看效果

Image

超级大坑

上面让 firstTableViewCell 根据 label 自动计算高度的过程中,有一个超级大坑:如果给左侧 UIImageView 赋的图片较大(大于 80px),将看到如下奇怪的结果:

Image

这只是因为图片把 UITableViewCell 撑大了,并不是我们的计算没有效果。

解决大坑:进攻是最好的防守!根据图片自动计算 firstTableViewCell 高度

首先,把图片的渲染模式改成 Aspect Fit:

Image

给 Images.xcassets 增加三张图片,命名为 0、1、2,尺寸从小到大:

Image

给 cellForRowAtIndexPath 增加代码:

if indexPath.row < 3 {
    cell.logoImageView.image = UIImage(named: indexPath.row.description)
}

查看效果:

Image

前两个 cell 看起来比较正常,第三个为什么多出了那么多空白?这就是使用 Auto Layout 限制图片宽度为 80px 的原生问题:宽度虽然限制了,高度却依然是原图的高度。解决办法也很简单:如果图片宽度大于 80px,就重绘一张 80px 宽度的图片填充进去。

新建一个 Group(虚拟文件夹),叫 Extensions,并在其内部新建 UIImage.swift 文件,内容如下:

import UIKit

extension UIImage {
    func resizeToSize(size: CGSize) -> UIImage {
        UIGraphicsBeginImageContext(size)
        self.drawInRect(CGRectMake(0, 0, size.width, size.height))
        let newImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        
        return newImage
    }
}

给 UIImage 类扩展了一个名为 resizeToSize 的方法,返回一个按照要求的大小重绘过的 UIImage 对象。修改 cellForRowAtIndexPath 的代码为:

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCellWithIdentifier("firstTableViewCell", forIndexPath: indexPath) as! firstTableViewCell

    cell.firstLabel.text = labelArray[indexPath.row]
    
    var image = UIImage(named: (indexPath.row % 3).description)!
    if image.size.width > 80 {
        image = image.resizeToSize(CGSizeMake(80, image.size.height * (80 / image.size.width)))
    }
    cell.logoImageView.image = image

    return cell
}

搞定!

查看效果

Image

从上图可以看出,cell 已经可以根据图片和文字中比较高的一个完全自适应。

致谢

感谢 《动态计算UITableViewCell高度详解》,给我提供了许多基础知识和灵感。


下一步:AutoAuto Layout 使用心得(六)—— 制造炫酷的下拉刷新动画

WRITTEN BY

avatar

评论:

chln
2017-03-10 18:06
var image = UIImage(named: (indexPath.row % 3).description)!
这句报错呢
god-long
2016-04-21 17:43
我用auto layout 做动画:
- (IBAction)labelAction:(id)sender {
    if (_isOpen) {
        _isOpen = NO;
        [self.view layoutIfNeeded];
        [UIView animateWithDuration:1.5f delay:.0f options:UIViewAnimationOptionCurveEaseInOut animations:^{
            self.labelTop.constant = self.labelTop.constant + 100;
            self.labelHeight.constant = self.labelHeight.constant - 100;
            [self.view layoutIfNeeded];
        } completion:^(BOOL finished) {

        }];
        
    }else {
        _isOpen = YES;
        [self.view layoutIfNeeded];
        [UIView animateWithDuration:1.5f delay:.0f options:UIViewAnimationOptionCurveEaseInOut animations:^{
            self.labelTop.constant = self.labelTop.constant - 100;
            self.labelHeight.constant = self.labelHeight.constant + 100;
            [self.view layoutIfNeeded];
        } completion:^(BOOL finished) {

        }];
    }
}

labelTop 是这个label距离super上边距,labelHeight是这个label的高度, self.labelHeight.constant 增大有动画,减少的时候没有动画,而self.labelTop.constant一直都有动画,请问,这是什么原因啊?
JohnLui
2016-04-21 17:46
@god-long:https://autolayout.club/2015/11/24/%E5%AD%95%E5%AD%95%E7%9A%84%E4%B8%89%E4%B8%AA%E9%97%AE%E9%A2%98%E4%B9%8B%E2%80%94%E2%80%94Auto-Layout-%E6%80%8E%E4%B9%88%E5%81%9A%E5%8A%A8%E7%94%BB/
325hang
2016-02-14 09:16
楼主,如果我在里面加个按钮点击事件,,怎么样才能让他跳到storybord的另一个页面上去!
lucifron
2016-01-25 00:07
resizeToSize方法感觉没有必要,不如直接outlet高度约束来更改直观。
YFZ
2016-01-15 01:04
非常实用,谢谢!
老初
2015-12-05 21:24
吕老师,为什么计算高度的时候最后面要加1?
“return cell.contentView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height + 1”
JohnLui
2015-12-07 11:08
@老初:为了 cell 底部的分割线
皇后
2015-12-28 22:07
@JohnLui:非常棒,吕老师,有没有oc版本的啊?
YFZ
2016-01-15 01:00
@JohnLui:太感谢了,都说IOS8 可以自动使用高度,但实际上怎么都不行,看了你的文章,马上就做好了。
youlong
2015-11-12 12:05
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath;

请问 为什么这个方法没有执行呢?
JohnLui
2015-11-12 12:25
@youlong:可能没有设置 dataSource?
KangKai
2015-10-04 10:32
老师,你说那个原生问题现在还有吗?请问还有没什么更简便的方法?
老初
2015-08-13 18:27
楼主,您好,请问不同尺寸的屏幕用不同大小的字体怎么设置啊?
Jerry
2015-07-13 22:23
感谢博主分享好文,按照您的方法实现了cell高度自适应 不过我遇到了一个问题,就是cell里面全是英文的时候,部分cell里的文字会被...代替,行高无法精确计算,中文就没有问题,不知道博主遇到了这个问题吗?
JohnLui
2015-07-13 22:59
@Jerry:没
Tech
2015-09-01 10:57
@Jerry:应该是你的计算option有问题哦
vanda
2015-07-10 11:08
支持楼主的风格,图文并茂,很生动。
木叶
2015-06-30 17:47
太麻烦了   保持图片的宽高比即可
Alex
2015-06-21 15:17
拜读。。谢谢。
like
2015-06-01 10:58
“Auto Layout 限制图片宽度为 80px 的原生问题”  真的有这个限制吗?
网上搜索也没有找到相关的介绍,苹果官方文档有没有相关的说明,麻烦楼主再详细一点。
JohnLui
2015-06-01 23:15
@like:文档里没写,但是实际出现了。虽然限制了图片的宽度,等比例对图片进行了缩小,但是 cell 高度的自动计算依然是按照缩小前的高度进行的。这应该是计算高度和图片渲染的顺序问题。
Cruisehu
2015-05-27 22:10
要兼容IOS7,不能使用你github中的新方法,但是设置行数为0还是只显示一行,后面三个点。求解?
JohnLui
2015-05-28 14:57
@Cruisehu:iOS 7 似乎没那么好搞。。。
Jingchao Yang
2015-05-12 14:03
能发份demo么。
JohnLui
2015-05-12 14:10
@Jingchao Yang:可以去 Github 看
Jingchao Yang
2015-05-12 14:11
@JohnLui:好,没太注意。。居然有github链接。
nene
2015-05-11 22:24
楼主文笔很好,讲授很清晰。
frankxzx
2015-04-29 20:43
博主 好厉害!
saya
2015-04-23 12:58
端午doge
andyhu
2015-04-23 11:13
确实很nice

发表评论:

© 2011-2019 岁寒  |  Powered by Emlog