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

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

问题描述

资源:

我从

我使用的蜂蜜图像的链接.我已将其大小设置为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

似乎不正确.

第一季度:为什么?

第二季度:我在调用cell.layoutSubviews之前和之后都记录了contentView的绑定,它从320切换到260,但是最终在viewDebugger中显示为 !!!

为什么contenView的边界再次改变?!

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

解决方案

我认为此问题与使用默认单元格的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-pt:/p>

如果需要,我们还可以通过将标签底部的底部限制为至少 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 \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])
    }
}

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

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天全站免登陆