如何正确获取 tableViewCell 的 contentView 绑定大小? [英] How to correctly get tableViewCell's contentView bound size?

查看:19
本文介绍了如何正确获取 tableViewCell 的 contentView 绑定大小?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

资源:

我从

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

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

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

解决方案

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

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

然后,在 cellForRowAt 中,您设置 .image 属性,并且它显示该操作设置内容视图高度.我在上面找不到任何文档,并且在调试中挖掘我找不到任何冲突的约束,所以我不完全确定为什么会这样.

两种选择:

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

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

选项2在这里:

类 MyTableViewCell: UITableViewCell {懒惰的 var customLabel : UILabel = {让 lbl = UILabel()lbl.translatesAutoresizingMaskIntoConstraints = falselbl.numberOfLines = 0lbl.setContentCompressionResistancePriority(.required, for: .vertical)返回 lbl}()懒惰的 var customImageView: UIImageView = {让 v = UIImageView()v.translatesAutoresizingMaskIntoConstraints = false返回 v}()覆盖初始化(样式:UITableViewCellStyle,reuseIdentifier:字符串?){super.init(风格:风格,reuseIdentifier:reuseIdentifier)设置布局()}私有函数 setupLayout(){contentView.addSubview(customLabel)contentView.addSubview(customImageView)//将 imageView 的前导限制为距 contentView 的前导 15-pts让 imgViewLeading = customImageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, 常量: 15)//将 imageView 的宽度限制为 42-pts让 imgViewWidth = customImageView.widthAnchor.constraint(equalToConstant: 42)//约束 imageView 的高度等于 imageView 的宽度让 imgViewHeight = customImageView.heightAnchor.constraint(equalTo: customImageView.widthAnchor, multiplier: 1.0)//imageView 垂直居中让 imgViewCenterY = customImageView.centerYAnchor.constraint(equalTo: contentView.centerYAnchor, 常量: 0.0)//还需要设置 imageView 的顶部和底部约束,//否则图像会超过单元格的高度//没有足够的文本来换行和扩展标签的高度//约束 imageView 的顶部距离单元格顶部 * 至少 * 4-pts让 imgViewTop = customImageView.topAnchor.constraint(greaterThanOrEqualTo: contentView.topAnchor, 常量: 4)//将 imageView 的底部限制为距离单元格底部*至少* 4-pts让 imgViewBottom = customImageView.topAnchor.constraint(lessThanOrEqualTo: contentView.bottomAnchor, 常量: -4)//将标签的顶部限制为距离单元格顶部*至少* 4-pts让 top = customLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 4)//如果您希望标签中的文本在单元格中垂直居中//将标签的底部限制为距离单元格底部 *exactly* 4-pts让底部= customLabel.bottomAnchor.constraint(equalTo:contentView.bottomAnchor,常量:-4)//如果您希望标签中的文本在单元格中顶部对齐//将标签的底部限制为距离单元格底部*至少* 4-pts//let bottom = customLabel.bottomAnchor.constraint(lessThanOrEqualTo: contentView.bottomAnchor, constant: -4)//将标签的前导限制为距图像尾随 5 分letleadingFromImage = customLabel.leadingAnchor.constraint(equalTo: customImageView.trailingAnchor, 常量: 5)//将标签的尾随约束到 contentView 的尾随让 trailing = customLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor)NSLayoutConstraint.activate([顶部,底部,leadingFromImage,尾随,imgViewLeading,imgViewCenterY,imgViewWidth,imgViewHeight,imgViewTop,imgViewBottom])}需要初始化?(编码器 aDecoder:NSCoder){致命错误()}}类 HoneyViewController: UIViewController {var 数据源 = [如果曼联输掉比赛,那将是美好的一天.无论如何,我希望明天阿森纳能赢得比赛",一条线.","两行
",]懒惰的 var tableView : UITableView = {让表 = UITableView()table.delegate = 自我table.dataSource = 自我table.translatesAutoresizingMaskIntoConstraints = falsetable.estimatedRowHeight = 100table.rowHeight = UITableViewAutomaticDimension返回表}()覆盖 func viewDidLoad() {super.viewDidLoad()view.addSubview(tableView)tableView.pinToAllEdges(of: view)tableView.register(MyTableViewCell.self, forCellReuseIdentifier: "id")}}扩展 HoneyViewController: UITableViewDelegate, UITableViewDataSource {func numberOfSections(in tableView: UITableView) ->诠释{返回 1}func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) ->诠释{返回数据源.count}func tableView(_tableView: UITableView, cellForRowAt indexPath: IndexPath) ->UITableViewCell {让 cell = tableView.dequeueReusableCell(withIdentifier: "id", for: indexPath) as!MyTableViewCellcell.customLabel.text = 数据源[indexPath.row]logInfo(来自:单元格)cell.accessoryType = .detailDisclosureButtoncell.customImageView.image = UIImage(命名:亲爱的")logInfo(来自:单元格)打印(" -  -  -  - -")返回单元格}私人 func logInfo(单元格:MyTableViewCell){print("boundsWidth: (cell.contentView.bounds.width) | maxLayoutWidth: (cell.contentView.bounds.width - 44 - 15 - 5) | systemLayoutSizeFitting : (cell.customLabel.systemLayoutSizeFitting(UILayoutFittingCompressedSize))")}}扩展 UIView{func pinToAllEdges(视图:UIView){letleading =leadingAnchor.constraint(equalTo: view.leadingAnchor)让 top = topAnchor.constraint(equalTo: view.topAnchor)让 trailing = trailingAnchor.constraint(equalTo: view.trailingAnchor)让底部 = bottomAnchor.constraint(equalTo: view.bottomAnchor)NSLayoutConstraint.activate([前导,顶部,尾随,底部])}}

需要更多的约束.如果单元格的文字只够一行(不换行),那么imageView的高度会超过单元格的高度:

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

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

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

我编辑的代码中的注释应该解释了这些差异.

Resources:

I've read multiple answers from Using Auto Layout in UITableView for dynamic cell layouts & variable row heights

And followed their suggestions but it's not working.

Setup to reproduce:

If you copy/paste the MyTableViewCell and ViewController snippets: then you can reproduce the issue.

I have subclassed MyTableViewCell and added my own label.

    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()
        }
    }

The following ViewController contains my tableview:

import UIKit

class ViewController: 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"]

    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])
    }
}

Link for honey image I used. I've set it's size to 44 * 44

Main issue

My major problem is inside 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
}

Questions:

For whatever reason the value assigned to:

cell.customLabel.preferredMaxLayoutWidth

doesn't seem to be right.

Q1: Why is that?

Q2: I'm logging the contentView's bound before and after I call cell.layoutSubviews and it switches from 320 to 260 but then eventually in the viewDebugger it shows up as 308!!!

Why is the contenView's bounds changing again?!

I've removed some other screenshots from the question. They were mostly clutter but maybe worth looking. You can take a look at the revision history.

解决方案

I believe the issue is related to using the default cell's imageView.

The image view itself doesn't exist until its .image property is set, so on your cell init you're constraining the custom label to an image view that is 0,0,0,0

Then, in cellForRowAt, you set the .image property, and it appears that action also sets the contentView height. I can't find any docs on it, and digging through in debug I can't find any conflicting constraints, so I'm not entirely sure why that's happening.

Two options:

1 - Instead of creating and adding a custom label, set the .numberOfLines on the default .textLabel to 0. That should be enough.

2 - If you need a customized label, also add a custom image view.

Option 2 is here:

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
Lines.",
    ]

    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])
    }
}

Edit:

A couple more constraints are needed. If the cell has only enough text for one line (no wrapping), the imageView height will exceed the height of the cell:

So, we add top and bottom constraints to the imageView to fit at least the top and bottom of the cell:

and, it will probably look a little better with some padding, so we constrain the top and bottom of the imageView to be at least 4-pts from the top and bottom of the cell:

If desired, we can also "top-align" the text in the label by constraining its bottom to be at least 4-pts from the bottom, instead of exactly 4-pts from the bottom:

The comments in my edited code should explain each of those differences.

这篇关于如何正确获取 tableViewCell 的 contentView 绑定大小?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆