UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

SOLVED: UITableView Automatic Row height programatically

Forums > iOS

I want the rows in my tableview to scale dynamicly based upon the content (2 labels, total of 3 lines of text). I keep getting this error (and my cells are set to standard size):

[Warning] Warning once only: Detected a case where constraints ambiguously suggest a height of zero for a table view cell's content view. We're considering the collapse unintentional and using standard height instead. Cell: <ProjectSammy.AnnotationCell: 0x106061d80; baseClass = UITableViewCell; frame = (0 647.5; 414 70); autoresize = W; layer = <CALayer: 0x282d09ee0>>

I think I've cohered to all of the requirements:

I fully constrain (top and bottom anchors) the labels in my custom cell:

    private func configureContents() {
        backgroundColor = .clear
        separatorInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)

        addSubviews(titleLabel, detailsLabel)

        NSLayoutConstraint.activate([
            titleLabel.topAnchor.constraint(equalTo: contentView.topAnchor,constant: 5),
            titleLabel.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor),
            titleLabel.trailingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.trailingAnchor),
            titleLabel.bottomAnchor.constraint(equalTo: detailsLabel.topAnchor),

            detailsLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor),
            detailsLabel.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor),
            detailsLabel.trailingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.trailingAnchor),
            detailsLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor)
        ])

    }

I set the rows on the TableView to automic dimension:

    private func configureTableView() {
        tableView = UITableView(frame: view.bounds, style: .grouped)
        view.addSubview(tableView)
        tableView.delegate = self
        tableView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        tableView.backgroundColor = .clear

        tableView.register(AnnotationCell.self, forCellReuseIdentifier: AnnotationCell.reuseIdentifier)
        tableView.register(AnnotationHeaderCell.self, forHeaderFooterViewReuseIdentifier: AnnotationHeaderCell.reuseIdentifier)

        tableView.sectionHeaderHeight = 40
        tableView.rowHeight = UITableView.automaticDimension
    }

And I even supply an estimated row heigth in the delagate method:

    func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
        return UITableView.automaticDimension
    }

    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return UITableView.automaticDimension
    }

    func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
        cell.layoutIfNeeded()
    }

Where do I go wrong ?

3      

Self-sizing cells are tricky and there are lot of ways it can go wrong...

Where are you calling configureContents? This should be called only once for cell so in the awakeFromNib method.

Also you have duplicate constraints:

titleLabel.bottomAnchor.constraint(equalTo: detailsLabel.topAnchor),

detailsLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor),

I would ditch manually setting frame for Table View as well as autoresizingMask mask and just use AutoLayout to pin it to all four edges.

3      

I also think this could be because of several problems. However, my strongest guess would be that you do not call translatesAutoResizingMaskIntoConstraints = false for your titleLabel and detailLabel before setting the constraints in your cell.

You actually do not need to override the delegate methods, you can set the estimatedRowHeight and rowHeight directly on the UITableView. If I remember correctly, as of iOS 13, the default setting for table view cells is that they have their heights set to automatic.

4      

Damn you are right @LoadingIndicator! (nice nickname by the way). The famous TAMIC likely strikes again.

3      

I should have said that TAMIC is set to false in my subclass of label, which both labels are ...

configureContents(0 is called from the init() Here's my whole custom cell class:

mport UIKit

class AnnotationCell: UITableViewCell, SelfConfiguringAnnotationCell {
    //MARK: - Properties
    let titleLabel = ProjectTitleLabel(withTextAlignment: .left, andFont: UIFont.preferredFont(forTextStyle: .headline))
    let detailsLabel = ProjectTitleLabel(withTextAlignment: .left, andFont: UIFont.preferredFont(forTextStyle: .subheadline), numberOfLines: 2)

    //MARK: - Init
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)

        configureContents()
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    //MARK: - Layout
    private func configureContents() {
        backgroundColor = .clear
        separatorInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)

//        titleLabel.backgroundColor = UIColor.tertiarySystemBackground.withAlphaComponent(0.20)

        addSubviews(titleLabel, detailsLabel)

        NSLayoutConstraint.activate([
            titleLabel.topAnchor.constraint(equalTo: contentView.topAnchor,constant: 5),
            titleLabel.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor),
            titleLabel.trailingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.trailingAnchor),
            titleLabel.bottomAnchor.constraint(equalTo: detailsLabel.topAnchor),

            detailsLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor),
            detailsLabel.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor),
            detailsLabel.trailingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.trailingAnchor),
            detailsLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor)
        ])

    }

    //MARK: - Configure cell with data
    func configure(with annotation: AnnotationsController.Annotation) {
        titleLabel.text = annotation.title
        detailsLabel.text = annotation.details
    }
}

And the ProjectTitleLabel subclass:

class ProjectTitleLabel: UILabel {

    override init(frame: CGRect) {
        super.init(frame: frame)
        self.configure()
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    //Conveniene init; you have to call the designated init always (self.init()). Ensures (in this case) that configure is always run!
    convenience init(withTextAlignment alignment: NSTextAlignment, andFont font: UIFont, andColor color: UIColor = .label, numberOfLines: Int = 1){
        self.init(frame: .zero)
        self.textAlignment = alignment
        self.font = font
        self.textColor = color
        self.numberOfLines = numberOfLines
    }

    private func configure() {
        adjustsFontSizeToFitWidth = true
        minimumScaleFactor = 0.85
        lineBreakMode = .byTruncatingTail
        translatesAutoresizingMaskIntoConstraints = false

        setContentCompressionResistancePriority(.required, for: .vertical)
    }
}

3      

That's weird. Could you try adding your labels as subViews to the contentView? Currently you just add it to the cell itself.

Haha, thanks @nemecek-filip :D

4      

@LoadingIndicator Woei! That's a grave oversight of mine, gonna change that immediatly!

    private func configureContents() {
        backgroundColor = .clear
        separatorInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)

        contentView.addSubviews(titleLabel,detailsLabel)

        NSLayoutConstraint.activate([
            titleLabel.topAnchor.constraint(equalTo: contentView.topAnchor,constant: 5),
            titleLabel.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor),
            titleLabel.trailingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.trailingAnchor),
            titleLabel.bottomAnchor.constraint(equalTo: detailsLabel.topAnchor),

            detailsLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor),
            detailsLabel.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor),
            detailsLabel.trailingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.trailingAnchor),
            detailsLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor)
        ])

    }

Also changed setup of the tableview to have an estimated rowheight set!

    private func configureTableView() {
        tableView = UITableView(frame: view.bounds, style: .grouped)
        view.addSubview(tableView)
        tableView.delegate = self
        tableView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        tableView.backgroundColor = .clear

        tableView.register(AnnotationCell.self, forCellReuseIdentifier: AnnotationCell.reuseIdentifier)
        tableView.register(AnnotationHeaderCell.self, forHeaderFooterViewReuseIdentifier: AnnotationHeaderCell.reuseIdentifier)

        tableView.sectionHeaderHeight = 40
        tableView.rowHeight = UITableView.automaticDimension
        tableView.estimatedRowHeight = 70
    }

And the delegate methods are no longer needed! Both together did the trick! Thank you so much! Sharp eyes 🦅

4      

Perfect, glad I was able to help :)

4      

Hacking with Swift is sponsored by Essential Developer

SPONSORED Join a FREE crash course for mid/senior iOS devs who want to achieve an expert level of technical and practical skills – it’s the fast track to being a complete senior developer! Hurry up because it'll be available only until April 28th.

Click to save your free spot now

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.