美文网首页iOSiOS技术专题Swift开发技巧
UITableViewCell AutoLayout动态行高总结

UITableViewCell AutoLayout动态行高总结

作者: 文兴 | 来源:发表于2016-03-30 17:06 被阅读10221次

最近在工作中遇到了一个问题,就是在AutoLayout中如何使UITableViewCell的行高根据内容(具体就是UILabel的多行显示)达到自适应高度。google so一通找到了一些资料,但都不是特别齐全,特别是针对iOS7的兼容上,因此也踩了不少坑,在这里做一个总结。

首先看一下效果

动态行高.png

其实在iOS8以上,cell的动态高度的实现已经变的很方便了,只需要你在cell里面正确设置约束,再设置tableview的几个属性,就大功告成了!

首先正确设置约束

设置约束.png
这里要注意的就是,一定要确保在设置约束之前,你UITableViewCell的size inspector里面 Row Height 是Default而不是custom的数值,否则之后不管你如何操作,UITableViewCell优先使用的都是custom的数值 行高为Default.png
还有一点要注意的是,如果你和我一样,是UILabel需要多行显示造成的行高不固定,那么你的UILabel的行数要设置为0,表示UILabel显示的是多行。 Lines为0.png

最后在viewDidLoad中加上

self.tableView.estimatedRowHeight = 56
self.tableView.rowHeight = UITableViewAutomaticDimension

estimatedRowHeight是假定的高度,因为需要预估UITableViewUIScrollViewcontentSize。因此这种方法可能潜在的问题就是数据量大的时候滚动条可能会闪动。具体的解决方法还没有研究,应该可以通过UIScrollView的代理方法解决。UITableViewAutomaticDimension这一句在iOS8+是作为rowHeight的默认值的,这句话也可以不写。

至此iOS8+ AutoLayout的动态行高就大功告成了,你甚至可以不用去实现heightForRowAtIndexPath

但是iOS7的兼容就显得蛋疼许多了,主要是由于,iOS7使用UITableView一定要实现heightForRowAtIndexPath代理方法,这里你可能会觉得,那我们实现这个代理方法,返回UITableViewAutomaticDimension就好了啊,遗憾的是UITableViewAutomaticDimension在iOS7里面也是不可用的,不信你可以试一下,直接crash。

所以我们的思路得这样走,实现heightForRowAtIndexPath, 创建一个临时的cell,设置cell里面各个view的属性,主动触发layout,通过UIView的方法systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)获取cell的实际尺寸,作为返回的高度。代码看起来像下面这样.

func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
        
        //兼容ios7
        if NSFoundationVersionNumber < NSFoundationVersionNumber_iOS_8_0 {
                let mockcell = tableView.dequeueReusableCellWithIdentifier(youtidentifier)
                //
                //设置你的cell的子view,比如UILabel的title
                //
                let height =  mockcell.contentView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height + 1
                return height
            }
        }
        else {
            return UITableViewAutomaticDimension
        }
    }

上面的代码需要注意的一点就是,tableView.dequeueReusableCellWithIdentifier这个方法千万不能传入indexPath,否则会死循环,一直调用该方法。

上面的代码其实还有一个很明显的问题,就是每一次计算高度都需要创建一次UITableViewCell,我们可以做一个简单的改进,用一个Dictionary<NSIndexPath,CGFloat>存储计算过的indexPath对应的height,代码如下。

private var heightOfIndex = [NSIndexPath:CGFloat]()
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
        
        //兼容ios7
        if NSFoundationVersionNumber < NSFoundationVersionNumber_iOS_8_0 {
            if let height = self.heightOfIndex[indexPath] {
                return height
            } else {
                let mockcell = tableView.dequeueReusableCellWithIdentifier(youtidentifier)
                //
                //设置你的cell的子view,比如UILabel的title
                //
                let height =  mockcell.contentView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height + 1
                self.heightOfIndex[indexPath] = height
                return height
            }
        }
        else {
            return UITableViewAutomaticDimension
        }
    }

为了能够代码重用,我们可以把他封装成一个类,设置cell的过程作为一个block传入。

class ALTableViewCellHeight {
    
    private var heightOfIndex = [NSIndexPath:CGFloat]()
    
    func heightForRowAtIndexPath(indexPath:NSIndexPath,initCell:()->UITableViewCell) -> CGFloat {
        if NSFoundationVersionNumber < NSFoundationVersionNumber_iOS_8_0 {
            if let height = self.heightOfIndex[indexPath] {
                return height
            } else {
                let cell = initCell()
                cell.layoutIfNeeded()
                let height =  cell.contentView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height + 1
                self.heightOfIndex[indexPath] = height
                return height
            }
        }
        else {
            return UITableViewAutomaticDimension
        }
    }
}

最后再说说iOS7下UILabel的一个坑,如果要使你的UILabel能够正常的多行显示,除了一开始说到的设置numberOfLines为0,还需要设置preferredMaxLayoutWidth,这个属性指的当换行的最大宽度。iOS8+能够通过AutoLayout计算出UILabel的宽度,把这个宽度作为preferredMaxLayoutWidth,但是iOS7下面不行,应该是一个bug。解决的方案是继承UILabel,重写layoutSubviews

class LabelDynamicHeight:UILabel {
    override func layoutSubviews() {
        super.layoutSubviews()
        self.preferredMaxLayoutWidth = self.frame.size.width
        super.layoutSubviews()
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        self.numberOfLines = 0
    }
}

相关文章

网友评论

  • biyuhuaping://设置行高
    - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
    return UITableViewAutomaticDimension;//自动尺寸
    }

    //预估行高
    - (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath{
    return 44;
    }
  • Counting_S:UITableViewAutomaticDimension 在iOS7会crash的原因是什么呀,谢谢
    文兴:@Counting_S 就是不支持这个特性呗…没什么特别的原因
  • 老薛有只猫:UITableViewAutomaticDimension not working until scroll?
  • 搜捕儿:除了UITableViewCell的size inspector里面 Row Height 设置成Default, cell里的label (也就是需要自适应文字的控件)的size inspector里面 也要把Preferred Width的Explict去掉勾选才行哦, 亲测
  • joymake:我的实现方式跟你的几乎一样、cellModel缓存高度、systemLayoutSizeFittingSize计算高度,不过是objc的 ,另外,autolayout 对于 IOs7来说就是个bug一样的存在
  • ifelseboyxx:还有 我看到 你在 heightForRow 里面 调用 “cell.layoutIfNeeded()” ,如果cell比较复杂,频繁地调用 cell.layoutIfNeeded() 会很卡的
    文兴:@_littleboy 这个你试验过么?可能我使用的时候cell比较简单没有发现。但是按照我的理解, layoutifNeeded只会在需要重新布局的时候如cell frame 改变时触发layout,理论上不存在卡顿问题。而且,在我的代码里面,cell.layoutIfNeeded()只会在未计算cell高度的时候调用,计算了高度的cell都存到了self.heightOfIndex里面,实际总调用次数应该等于cell的个数,并没有频繁的调用。
  • ifelseboyxx:" iOS8+能够通过AutoLayout计算出UILabel的宽度,把这个宽度作为preferredMaxLayoutWidth," 博主,preferredMaxLayoutWidth 这个属性你一般是怎么设置的? 这个问题纠结我很多天了,我用 xib 画 cell 比较多,所以正常在 awakeFromNib 里面设置 类似: “self.lblTheme.preferredMaxLayoutWidth = [UIScreen mainScreen].bounds.size.width - 83.0f;” ,这个83 是 label 固定的左右间距,这样很不好!我想知道,用xib 怎么才能拿到正确的 label 的尺寸,比如 self.lblTheme.preferredMaxLayoutWidth = CGRectGetWidth(self.lblTheme.frame); 但是 CGRectGetWidth(self.lblTheme.frame) 那不要正确的尺寸
  • 罗同学_:还有就是 iOS8以上我用你的方法返回的行高也不正确,求解答。。
    文兴:@单手两万行无bug 正确设置约束是前提
  • 罗同学_:为什么 我用 self.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize 这个方法返回的值是0 啊 :grin:
    文兴:@单手两万行无bug 你用的是autolayout么?

本文标题:UITableViewCell AutoLayout动态行高总结

本文链接:https://www.haomeiwen.com/subject/kvmrlttx.html