UITableViewCell 高度根据行内容使用自动布局动态计算

Posted

技术标签:

【中文标题】UITableViewCell 高度根据行内容使用自动布局动态计算【英文标题】:UITableViewCell height dynamically calculated with autolayout based on row content 【发布时间】:2018-11-17 15:58:49 【问题描述】:

我在 nib 文件中准备了 UITableViewCell,所有约束从上到下。在表格视图中计算行高似乎可行。

但是,如果我想根据某些行数据条件激活/停用单元格中的其他一些约束,那么我会遇到问题,因为当时似乎没有计算高度。问题是什么?在以下示例中,TableRowDM.hidden 表示是否隐藏字幕。我通过激活未激活的约束(常量 = 0 的高度)并将 titleLabel 和 subtitleLabel 之间的垂直间隙设置为 0 来隐藏此 UILabel。

struct TableRowDM 
    let title: String
    let subTitle: String
    let hidden: Bool


let cellId: String = "CellView"

class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource 

    @IBOutlet weak var tableView: UITableView!

    let rowData: [TableRowDM] = [
        TableRowDM(title: "Row 1", subTitle: "Short title 1th row", hidden: false),
        TableRowDM(title: "Row 2", subTitle: "Short title 2th row", hidden: true),
        TableRowDM(title: "Row 3", subTitle: "Very long text in subtitle at 3th row to test text wrapping and growing subtitle height", hidden: false),
        TableRowDM(title: "Row 4", subTitle: "Long text in subtitle at 4th row", hidden: false),
        TableRowDM(title: "Row 5", subTitle: "Long text in subtitle at 5th row", hidden: true),
        TableRowDM(title: "Row 6", subTitle: "Long text in subtitle at 6th row", hidden: false),
    ]

    override func viewDidLoad() 
        super.viewDidLoad()
        tableView.rowHeight = UITableViewAutomaticDimension
        tableView.estimatedRowHeight = 50
        tableView.tableFooterView = UIView(frame: CGRect.zero)
        tableView.register(UINib(nibName: cellId, bundle: nil), forCellReuseIdentifier: cellId)
        tableView.delegate = self
        tableView.dataSource = self
    

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell 
        let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as! CellView
        let row: TableRowDM = self.rowData[indexPath.row]

        cell.title.text = row.title
        cell.subtitle.text = row.subTitle

        if row.hidden 
            // hide subtitle
            cell.subtitleHeight.isActive = true
            cell.titleToSubtitleGap.constant = 0
         else 
            // show subtitle
            cell.subtitleHeight.isActive = false
            cell.titleToSubtitleGap.constant = 16
        
        cell.setNeedsLayout()
        cell.layoutIfNeeded()
        return cell
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int 
        return rowData.count
    

这似乎不起作用,并且 subtitleLabel 没有隐藏并且行高始终相同。当 subtitleLabel 和 titleLabel 的内容发生变化时,高度会调整为这些 UIView 内的文本,但是当我尝试使用约束进行操作时,它就不起作用了。有什么帮助吗?我究竟做错了什么? CellView 是带有此类子视图的非常简单的视图

-----------------
      |
- titleLabel ----
      |
      | <- titleToSubtitleGap
      |
- subtitleLabel -
      |
-----------------

【问题讨论】:

【参考方案1】:

我为我的问题找到了解决方案。它没有在任何地方记录,但它可以工作。

什么是重要的。如果您支持横向/纵向方向,那么重要的是在 NSLayoutConstraints 中设置正确的优先级,该优先级定义垂直完整的约束链和视图(具有定义的高度),如 here 所述 - 他们写道:

接下来,在单元格内容中布置表格视图单元格的内容 看法。要定义单元格的高度,您需要一个完整的链 约束和视图(具有定义的高度)来填充之间的区域 内容视图的上边缘和下边缘。如果您的观点有 内在内容高度,系统使用这些值。如果没有,你 必须将适当的高度约束添加到视图或 内容视图本身。

他们没有写的是 如果您需要支持许多设备方向,则最终定义表格单元格contentView 的高度的所有那些垂直约束必须具有低于所需 (1000) 的优先级。我在示例中将这些优先级设置为高 (750)。

接下来,在updateConstraints 方法中使用这些约束进行更改很重要。因此,如果您正在设计 UIView 并且当此视图的某些属性需要更改约束时,您需要在您的视图中执行类似的操作,以便在适当的时候调用 updateConstraints 方法。设置属性新值后调用needsUpdateConstraints(),这样 AutoLayout 就会知道我们 UIView 中的约束发生了变化。

import UIKit

class ListItemTableViewCell: UITableViewCell 

    @IBOutlet weak var subtitleToTitleGap: NSLayoutConstraint!
    @IBOutlet weak var subtitleHeightZero: NSLayoutConstraint!

    @IBOutlet weak var titleLabel: UILabel!
    @IBOutlet weak var subtitleLabel: UILabel!

    public var isSubtitleHidden: Bool 
        get 
            return self.subtitleLabel.isHidden
        
        set 
            // Here we are changing property that change layout in our view
            self.subtitleLabel.isHidden = newValue
            self.needsUpdateConstraints() // Here we are telling AutoLayout to recalculate constraints in `updateConstraints` method.
        
    

#if !TARGET_INTERFACE_BUILDER
    // We need to block this code from running in Interface Builder
    override func updateConstraints() 
        if self.isSubtitleHidden 
            // Hide Subtitle
            self.subtitleToTitleGap.constant = 0.0
            self.subtitleHeightZero.autoInstall() // This makes subtitle height to be 0
         else 
            // Show Subtitle
            self.subtitleToTitleGap.constant = 2.0
            self.subtitleHeightZero.autoRemove() // This will make subtitle height to be self calculated
        
        super.updateConstraints() // don't forget to call this after You finished constraints changes
    
#endif

对我来说最重要也是最难发现的是从界面构建器中隐藏updateConstraints 的实现,因为它会导致 IB 在 xib 编辑器中不时崩溃。看上面这个例子。该方法包含在#if 预处理器宏中。所以我认为就是这样。祝你好运!

【讨论】:

以上是关于UITableViewCell 高度根据行内容使用自动布局动态计算的主要内容,如果未能解决你的问题,请参考以下文章

带有 UIWebview 的 UITableViewCell,可根据内容大小调整高度

如何根据 UITableViewCell 中的内容调整水平 UICollectionView 的高度

自动布局 4 个 UILabel,根据其内容/文本垂直高度

UITableViewCell嵌套UIWebView并且cell根据webView的内容自适应

在 UITableViewCell 中加载 UIWebView 并根据 UIWebview 的内容制作 UITabViewCell 的高度

根据 iOS SWIFT 高度更高的 UIImageView 或 UILabel 设置 UITableViewCell 高度