如何正确获取 tableViewCell 的 contentView 绑定大小?

Posted

技术标签:

【中文标题】如何正确获取 tableViewCell 的 contentView 绑定大小?【英文标题】:How to correctly get tableViewCell's contentView bound size? 【发布时间】:2018-10-08 18:11:35 【问题描述】:

资源:

我已经阅读了来自Using Auto Layout in UITableView for dynamic cell layouts & variable row heights 的多个答案

并遵循他们的建议,但它不起作用。

设置重现:

如果您复制/粘贴 MyTableViewCellViewController sn-ps: 那么您可以重现该问题。

我已将 MyTableViewCell 子类化并添加了我自己的标签。

    import UIKit

    class MyTableViewCell: UITableViewCell 

        lazy var customLabel : UILabel = 
            let lbl = UILabel()
            lbl.translatesAutoresizingMaskIntoConstraints = false
            lbl.numberOfLines = 0
            return lbl
        ()

        override init(style: UITableViewCellStyle, reuseIdentifier: String?) 
            super.init(style: style, reuseIdentifier: reuseIdentifier)
            setupLayout()
        
        private func setupLayout()
            contentView.addSubview(customLabel)

            let top = customLabel.topAnchor.constraint(equalTo: contentView.topAnchor)
            let bottom = customLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor)
            let leadingFromImage = customLabel.leadingAnchor.constraint(equalTo: imageView!.trailingAnchor, constant: 5)
            let trailing = customLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor)

            NSLayoutConstraint.activate([top, bottom, leadingFromImage, trailing])
        

        required init?(coder aDecoder: NSCoder) 
            fatalError()
        
    

以下ViewController 包含我的表格视图:

import UIKit

class ViewController: UIViewController 

    var datasource = ["It would have been a great day had Manchester United Lost its \n game. Anyhow I hope tomorrow Arsenal will win the game"]

    lazy var tableView : UITableView = 
        let table = UITableView()
        table.delegate = self
        table.dataSource = self
        table.translatesAutoresizingMaskIntoConstraints = false
        table.estimatedRowHeight = 100
        table.rowHeight = UITableViewAutomaticDimension
        return table
    ()
    override func viewDidLoad() 
        super.viewDidLoad()
        view.addSubview(tableView)
        tableView.pinToAllEdges(of: view)
        tableView.register(MyTableViewCell.self, forCellReuseIdentifier: "id")
    


extension ViewController: UITableViewDelegate, UITableViewDataSource 

    func numberOfSections(in tableView: UITableView) -> Int 
        return 1
    

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int 
        return datasource.count
    

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell 
        let cell = tableView.dequeueReusableCell(withIdentifier: "id", for: indexPath) as! MyTableViewCell

        cell.customLabel.text = datasource[indexPath.row]
        logInfo(of: cell)

        cell.accessoryType = .detailDisclosureButton
        cell.imageView?.image = UIImage(named: "honey")
        cell.layoutSubviews()
        cell.customLabel.preferredMaxLayoutWidth = tableView.bounds.width
        logInfo(of: cell)
        print("---------")

        return cell
    

    private func logInfo(of cell: MyTableViewCell)
        print("boundsWidth: \(cell.contentView.bounds.width) | maxLayoutWidth: \(cell.contentView.bounds.width - 44 - 15 - 5) | systemLayoutSizeFitting : \(cell.customLabel.systemLayoutSizeFitting(UILayoutFittingCompressedSize))")
        


extension UIView

    func pinToAllEdges(of view: UIView)
        let leading = leadingAnchor.constraint(equalTo: view.leadingAnchor)
        let top = topAnchor.constraint(equalTo: view.topAnchor)
        let trailing = trailingAnchor.constraint(equalTo: view.trailingAnchor)
        let bottom = bottomAnchor.constraint(equalTo: view.bottomAnchor)        

        NSLayoutConstraint.activate([leading, top, trailing, bottom])
    

我使用了honey image 的链接。我已将其大小设置为44 * 44

主要问题

我的主要问题在cellForRowAtIndex内部:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell 
    let cell = tableView.dequeueReusableCell(withIdentifier: "id", for: indexPath) as! MyTableViewCell

    cell.customLabel.text = datasource[indexPath.row]
    logInfo(of: cell)

    cell.accessoryType = .detailDisclosureButton
    cell.imageView?.image = UIImage(named: "honey")
    cell.layoutSubviews()
    cell.customLabel.preferredMaxLayoutWidth = cell.contentView.bounds.width
    logInfo(of: cell)
    print("---------")

    return cell

问题:

无论出于何种原因,赋值给:

cell.customLabel.preferredMaxLayoutWidth

似乎不对。

Q1:为什么会这样?

Q2:我在调用 cell.layoutSubviews 之前和之后记录 contentView 的绑定,它从 320 切换到 260 但最终在 viewDebugger 中显示为 @987654338 @!!!

为什么 contenView 的边界又变了?!

我已经从问题中删除了一些其他屏幕截图。它们大多杂乱无章,但也许​​值得一看。您可以查看修订历史记录。

【问题讨论】:

也许覆盖MyTableViewCell 中的布局方法,如layoutSubviews 并在preferredMaxLayoutWidth 中设置preferredMaxLayoutWidth 而不是cellForRowAt: 会起作用 我的代码当前序列不应该有同样的效果吗? 忽略边界变化,你想完成什么?很有可能您一开始就不需要该值... @DonMag 这是问题初始版本中的image1。如果我没有在customLabel 上设置任何宽度约束,就会发生这种情况,它的侧面只有 4 个约束。使用preferredMaxLayoutWidth 我正在尝试为其设置宽度。因此标签的高度将驱动该单元格所需的最大高度。我将cell.contentView.bounds.width 分配给标签的preferredMaxLayoutWidth。但这并不准确。所以我在拨打layoutSubviews 后分配了preferredMaxLayoutWidth。 UI 看起来更好,但并不完美 【参考方案1】:

我认为该问题与使用默认单元格的 imageView 有关。

在设置.image 属性之前,图像视图本身不存在,因此在单元格初始化时,您将自定义标签限制为 0,0,0,0 的图像视图

然后,在cellForRowAt 中,您设置了.image 属性,并且显示该操作设置了contentView 的高度。我在上面找不到任何文档,并且在调试中挖掘我找不到任何冲突的约束,所以我不完全确定为什么会发生这种情况。

两种选择:

1 - 将默认.textLabel 上的.numberOfLines 设置为0,而不是创建和添加自定义标签。 应该足够了。

2 - 如果您需要自定义标签,添加自定义图像视图。

选项2在这里:

class MyTableViewCell: UITableViewCell 

    lazy var customLabel : UILabel = 
        let lbl = UILabel()
        lbl.translatesAutoresizingMaskIntoConstraints = false
        lbl.numberOfLines = 0
        lbl.setContentCompressionResistancePriority(.required, for: .vertical)
        return lbl
    ()

    lazy var customImageView: UIImageView = 
        let v = UIImageView()
        v.translatesAutoresizingMaskIntoConstraints = false
        return v
    ()

    override init(style: UITableViewCellStyle, reuseIdentifier: String?) 
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        setupLayout()
    

    private func setupLayout()
        contentView.addSubview(customLabel)

        contentView.addSubview(customImageView)

        // constrain leading of imageView to be 15-pts from the leading of the contentView
        let imgViewLeading = customImageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 15)

        // constrain width of imageView to 42-pts
        let imgViewWidth = customImageView.widthAnchor.constraint(equalToConstant: 42)

        // constrain height of imageView to be equal to width of imageView
        let imgViewHeight = customImageView.heightAnchor.constraint(equalTo: customImageView.widthAnchor, multiplier: 1.0)

        // center imageView vertically
        let imgViewCenterY = customImageView.centerYAnchor.constraint(equalTo: contentView.centerYAnchor, constant: 0.0)

        // top and bottom constraints for the imageView also need to be set,
        // otherwise the image will exceed the height of the cell when there
        // is not enough text to wrap and expand the height of the label

        // constrain top of imageView to be *at least* 4-pts from the top of the cell
        let imgViewTop = customImageView.topAnchor.constraint(greaterThanOrEqualTo: contentView.topAnchor, constant: 4)

        // constrain bottom of imageView to be *at least* 4-pts from the bottom of the cell
        let imgViewBottom = customImageView.topAnchor.constraint(lessThanOrEqualTo: contentView.bottomAnchor, constant: -4)

        // constrain top of the label to be *at least* 4-pts from the top of the cell
        let top = customLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 4)

        // if you want the text in the label vertically centered in the cell
        // constrain bottom of the label to be *exactly* 4-pts from the bottom of the cell
        let bottom = customLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -4)

        // if you want the text in the label top-aligned in the cell
        // constrain bottom of the label to be *at least* 4-pts from the bottom of the cell
        // let bottom = customLabel.bottomAnchor.constraint(lessThanOrEqualTo: contentView.bottomAnchor, constant: -4)

        // constrain leading of the label to be 5-pts from the trailing of the image
        let leadingFromImage = customLabel.leadingAnchor.constraint(equalTo: customImageView.trailingAnchor, constant: 5)

        // constrain the trailing of the label to the trailing of the contentView
        let trailing = customLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor)

        NSLayoutConstraint.activate([
            top, bottom, leadingFromImage, trailing,
            imgViewLeading, imgViewCenterY, imgViewWidth, imgViewHeight,
            imgViewTop, imgViewBottom
            ])

    

    required init?(coder aDecoder: NSCoder) 
        fatalError()
    


class HoneyViewController: UIViewController 

    var datasource = [
        "It would have been a great day had Manchester United Lost its game. Anyhow I hope tomorrow Arsenal will win the game",
        "One line.",
        "Two\nLines.",
    ]

    lazy var tableView : UITableView = 
        let table = UITableView()
        table.delegate = self
        table.dataSource = self
        table.translatesAutoresizingMaskIntoConstraints = false
        table.estimatedRowHeight = 100
        table.rowHeight = UITableViewAutomaticDimension
        return table
    ()

    override func viewDidLoad() 
        super.viewDidLoad()
        view.addSubview(tableView)
        tableView.pinToAllEdges(of: view)
        tableView.register(MyTableViewCell.self, forCellReuseIdentifier: "id")
    


extension HoneyViewController: UITableViewDelegate, UITableViewDataSource 

    func numberOfSections(in tableView: UITableView) -> Int 
        return 1
    

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int 
        return datasource.count
    

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell 
        let cell = tableView.dequeueReusableCell(withIdentifier: "id", for: indexPath) as! MyTableViewCell

        cell.customLabel.text = datasource[indexPath.row]
        logInfo(of: cell)

        cell.accessoryType = .detailDisclosureButton
        cell.customImageView.image = UIImage(named: "Honey")
        logInfo(of: cell)
        print("---------")

        return cell
    

    private func logInfo(of cell: MyTableViewCell)
        print("boundsWidth: \(cell.contentView.bounds.width) | maxLayoutWidth: \(cell.contentView.bounds.width - 44 - 15 - 5) | systemLayoutSizeFitting : \(cell.customLabel.systemLayoutSizeFitting(UILayoutFittingCompressedSize))")
    


extension UIView

    func pinToAllEdges(of view: UIView)
        let leading = leadingAnchor.constraint(equalTo: view.leadingAnchor)
        let top = topAnchor.constraint(equalTo: view.topAnchor)
        let trailing = trailingAnchor.constraint(equalTo: view.trailingAnchor)
        let bottom = bottomAnchor.constraint(equalTo: view.bottomAnchor)

        NSLayoutConstraint.activate([leading, top, trailing, bottom])
    

编辑:

需要更多的约束。如果单元格只有一行的文本(不换行),则imageView的高度会超过单元格的高度:

所以,我们为 imageView 添加顶部和底部约束以适应至少单元格的顶部和底部:

而且,如果有一些填充,它可能看起来会好一些,所以我们将 imageView 的顶部和底部限制为 至少 距离单元格的顶部和底部 4-pts:

如果需要,我们还可以将标签中的文本“顶部对齐”,方法是将其底部限制为距底部至少 4 个点,而不是正好 从底部算起 4 分:

我编辑的代码中的 cmets 应该解释这些差异。

【讨论】:

不要创建和添加自定义标签,而是将默认 .textLabel 上的 .numberOfLines 设置为 0。这应该足够了。 同意。但我不想那样做。我想解决这个问题。深入了解它,看看它为什么不起作用:)。让我试试你的解决方案... 我实际上记得当我编写这个代码时,我在想imageView!.trailingAnchor 怎么没有失败。我的意思是,如果它是可选的而不是 nil 在实例化时虽然是空的,那么这很奇怪:/ 我真的不明白你为什么要从代码中编写布局......但是 nvm 。在情节提要或 xib 中创建自定义单元格(如果您制作 xib,则需要在 tableview 上注册它)@Honey 是正确的,制作自定义图像视图和自定义标签正确设置约束。将行数设置为 0,换行符设置为 0。只要您正确设置了约束,单元格就应该展开。 (不要忘记将单元格行高设置为自动并估计为您想要的数字) 从代码中编写布局时,您很可能会在那里犯错,并且会花下一个小时找出错误所在。 @JaubSanestrzik - 使用代码代替(或连同)Storyboards / Interface Builder 的许多、许多、许多理由。这种情况下的问题似乎与不正确的约束无关。

以上是关于如何正确获取 tableViewCell 的 contentView 绑定大小?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用自定义 tableviewcells 正确添加和删除对象到 tableview?

如何正确加载依赖的 tableViewCell API 数据?

如何在 tableviewcell 中获取 indexpath uitextfield?

tableViewcell页码

如何在 TableViewCell 类中的 TextField 委托方法上获取单元格索引

如何在目标 c 中的视图控制器上获取自定义 TableViewCell 值