Search code examples
swiftuitableviewuistackviewuitableviewautomaticdimension

UIStackView with multiline label in a UITableViewCell incorrect height


I have a table view where each cell displays an optional title and a multiline subtitle. I want the cell to be self-sizing (i.e grow along with the subtitle but stay as compact as possible). For that I use tableView.rowHeight = UITableView.automaticDimension

The problem I have is that there is quite a lot of vertical spacing around the subtitle in the cells that do have a title.

Cells without title are compressed correctly. Also when I reload the table view, all the layout becomes correct.

Expected behaviour:

enter image description here

Actual behaviour:

enter image description here

The cell has basically a UIStackView pinned to the cell's contentView.

import UIKit

public class TableViewCellSubtitle: UITableViewCell {

    private lazy var labelStack: UIStackView = {
        let labelStack = UIStackView()
        labelStack.alignment = .fill
        labelStack.distribution = .fillProportionally
        labelStack.axis = .vertical
        return labelStack
    }()

    private lazy var titleLabel: UILabel = {
        let titleLabel = UILabel()
        titleLabel.backgroundColor = UIColor.blue.withAlphaComponent(0.3)
        return titleLabel
    }()

    private lazy var subtitleLabel: UILabel = {
        let subtitleLabel = UILabel()
        subtitleLabel.numberOfLines = 0
        subtitleLabel.backgroundColor = UIColor.green.withAlphaComponent(0.3)
        return subtitleLabel
    }()

    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)

        setupUI()
        setupConstraints()
    }

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

    private func setupUI() {

        contentView.addSubview(labelStack)
        labelStack.translatesAutoresizingMaskIntoConstraints = false
        labelStack.addArrangedSubview(titleLabel)
        labelStack.addArrangedSubview(subtitleLabel)

    }

    private func setupConstraints() {
        NSLayoutConstraint.activate([
                labelStack.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16),
                labelStack.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 12),
                contentView.bottomAnchor.constraint(equalTo: labelStack.bottomAnchor, constant: 12),
                contentView.trailingAnchor.constraint(equalTo: labelStack.trailingAnchor, constant: 16)
            ])
    }

    public func setup(title: String?, subtitle: String) {
        titleLabel.text = title
        if title == nil {
            labelStack.removeArrangedSubview(titleLabel)
            titleLabel.removeFromSuperview()
        }
        subtitleLabel.text = subtitle
    }
}

I tried setting the content hugging on the subtitle

subtitleLabel.setContentHuggingPriority(.required, for: .vertical)

but that makes the title to grow:

enter image description here

If I set it to both:

titleLabel.setContentHuggingPriority(.required, for: .vertical)
subtitleLabel.setContentHuggingPriority(.required, for: .vertical)

it becomes

enter image description here

In all the cases, if I reload the cell or the table view, the layout becomes correct (here on cell tap):

enter image description here

I'm aware that I could layout the cell without using the stack view but my real implementation is a bit more complex


Solution

    • Try with fill instead of fill proportional
    • Or try to set label.sizeToFit() to collapse label to its content size