kmell96
kmell96

Reputation: 1465

UITableView with automaticDimension doesn't dynamically increase size due to NSLayoutConstraint error

I'm creating a custom UITableViewCell. For now, all I want is for it to have a UIButton on the left (checkButton), and two UILabels (titleLabel and notesLabel) to the right of the button.

Basically, it should look like a standard UITableViewCell with an image and two text labels (but please don't tell me to just re-use a standard cell, because I can't do this for a variety of reasons). The button should have a fixed size (16x16) and be vertically centered in the cell. The two labels should line wrap and expand to fit their content. I'm trying to define this cell programmatically, so I've created the below initializer to define the constraints.

override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
    super.init(style: style, reuseIdentifier: reuseIdentifier)
    titleLabel.translatesAutoresizingMaskIntoConstraints = false
    titleLabel.font = UIFont.systemFont(ofSize: 16)
    titleLabel.lineBreakMode = .byWordWrapping
    titleLabel.numberOfLines = 0
    contentView.addSubview(titleLabel)
    checkButton.translatesAutoresizingMaskIntoConstraints = false
    contentView.addSubview(checkButton)
    notesLabel.translatesAutoresizingMaskIntoConstraints = false
    notesLabel.font = UIFont.systemFont(ofSize: 13)
    notesLabel.lineBreakMode = .byWordWrapping
    notesLabel.numberOfLines = 0
    contentView.addSubview(notesLabel)
    addConstraint(NSLayoutConstraint(item: titleLabel,
                                     attribute: .top,
                                     relatedBy: .equal,
                                     toItem: contentView,
                                     attribute: .top,
                                     multiplier: 1,
                                     constant: 0))
    addConstraint(NSLayoutConstraint(item: notesLabel,
                                     attribute: .top,
                                     relatedBy: .equal,
                                     toItem: titleLabel,
                                     attribute: .bottom,
                                     multiplier: 1,
                                     constant: 0))
    addConstraint(NSLayoutConstraint(item: titleLabel,
                                     attribute: .trailing,
                                     relatedBy: .equal,
                                     toItem: contentView,
                                     attribute: .trailing,
                                     multiplier: 1,
                                     constant: -10))
    addConstraint(NSLayoutConstraint(item: notesLabel,
                                     attribute: .trailing,
                                     relatedBy: .equal,
                                     toItem: contentView,
                                     attribute: .trailing,
                                     multiplier: 1,
                                     constant: -10))
    addConstraint(NSLayoutConstraint(item: notesLabel,
                                     attribute: .bottom,
                                     relatedBy: .equal,
                                     toItem: contentView,
                                     attribute: .bottom,
                                     multiplier: 1,
                                     constant: 0))
    addConstraint(NSLayoutConstraint(item: checkButton,
                                     attribute: .leading,
                                     relatedBy: .equal,
                                     toItem: contentView,
                                     attribute: .leading,
                                     multiplier: 1,
                                     constant: 20))
    addConstraint(NSLayoutConstraint(item: checkButton,
                                     attribute: .centerY,
                                     relatedBy: .equal,
                                     toItem: contentView,
                                     attribute: .centerY,
                                     multiplier: 1,
                                     constant: 0))
    addConstraint(NSLayoutConstraint(item: checkButton,
                                     attribute: .height,
                                     relatedBy: .equal,
                                     toItem: nil,
                                     attribute: .notAnAttribute,
                                     multiplier: 0,
                                     constant: 16))
    addConstraint(NSLayoutConstraint(item: checkButton,
                                     attribute: .width,
                                     relatedBy: .equal,
                                     toItem: nil,
                                     attribute: .notAnAttribute,
                                     multiplier: 0,
                                     constant: 16))
    addConstraint(NSLayoutConstraint(item: notesLabel,
                                     attribute: .leading,
                                     relatedBy: .equal,
                                     toItem: checkButton,
                                     attribute: .trailing,
                                     multiplier: 1,
                                     constant: 12))
    addConstraint(NSLayoutConstraint(item: titleLabel,
                                     attribute: .leading,
                                     relatedBy: .equal,
                                     toItem: checkButton,
                                     attribute: .trailing,
                                     multiplier: 1,
                                     constant: 12))
}

When I run this code, it works mostly as expected, except that Xcode prints the following warning: [Warning] Warning once only: Detected a case where constraints ambiguously suggest a height of zero for a tableview cell's content view. We're considering the collapse unintentional and using standard height instead.

I'd normally just ignore this, but it seems to be preventing the cell from expanding to fit its content. For example, if one of the labels has enough content to expand to 3 lines, only the first line appears. The behavior I want is for the labels (and by extension, the cell) to expand to fit their content. What am I doing wrong with the height constraints?

Upvotes: 0

Views: 95

Answers (2)

AamirR
AamirR

Reputation: 12208

Here is my approach

private func setUp() {
    self.selectionStyle = .none
    self.contentView.backgroundColor = .white

    let button = UIButton(frame: CGRect(x: 0, y: 0, width: 16, height: 16))
    button.setImage(UIImage(named: "arrow"), for: .normal)
    button.translatesAutoresizingMaskIntoConstraints = false
    self.contentView.addSubview(button)
    button.leadingAnchor.constraint(equalTo: self.contentView.leadingAnchor, constant: 8.0).isActive = true
    button.centerYAnchor.constraint(equalTo: self.contentView.centerYAnchor).isActive = true
    button.widthAnchor.constraint(equalToConstant: button.frame.width).isActive = true
    button.heightAnchor.constraint(equalToConstant: button.frame.height).isActive = true

    let view = UIView()
    view.backgroundColor = .red
    view.translatesAutoresizingMaskIntoConstraints = false
    self.contentView.addSubview(view)
    view.topAnchor.constraint(equalTo: self.contentView.topAnchor, constant: 8.0).isActive = true
    view.leadingAnchor.constraint(equalTo: self.contentView.leadingAnchor, constant: button.frame.width + 16.0).isActive = true
    view.trailingAnchor.constraint(equalTo: self.contentView.trailingAnchor, constant: 8.0).isActive = true
    view.bottomAnchor.constraint(equalTo: self.contentView.bottomAnchor, constant: -8.0).isActive = true

    titleLabel.translatesAutoresizingMaskIntoConstraints = false
    titleLabel.numberOfLines = 0
    view.addSubview(titleLabel)
    titleLabel.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
    titleLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
    titleLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true

    notesLabel.translatesAutoresizingMaskIntoConstraints = false
    notesLabel.numberOfLines = 0
    view.addSubview(notesLabel)
    notesLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 8.0).isActive = true
    notesLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
    notesLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
    notesLabel.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
}

If the labels are multiline dynamic height, Use following to enable Self-Sizing cells

tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = 44.0

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

That yields

enter image description here

Upvotes: 0

Muhammad Waqas Bhati
Muhammad Waqas Bhati

Reputation: 2805

NOTE: Please add your Label's and Button's in Cell not in contentView of the cell. below code will work, please check and let me know incase of any issue.

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

    addSubview(titleLabel)
    addSubview(checkButton)
    addSubview(notesLabel)

    titleLabel.translatesAutoresizingMaskIntoConstraints = false
    titleLabel.font = UIFont.systemFont(ofSize: 16)
    titleLabel.lineBreakMode = .byWordWrapping
    titleLabel.numberOfLines = 0

    checkButton.translatesAutoresizingMaskIntoConstraints = false

    notesLabel.translatesAutoresizingMaskIntoConstraints = false
    notesLabel.font = UIFont.systemFont(ofSize: 13)
    notesLabel.lineBreakMode = .byWordWrapping
    notesLabel.numberOfLines = 0

    addConstraint(NSLayoutConstraint(item: titleLabel,
                                     attribute: .top,
                                     relatedBy: .equal,
                                     toItem: self,
                                     attribute: .top,
                                     multiplier: 1,
                                     constant: 0))
    addConstraint(NSLayoutConstraint(item: notesLabel,
                                     attribute: .top,
                                     relatedBy: .equal,
                                     toItem: titleLabel,
                                     attribute: .bottom,
                                     multiplier: 1,
                                     constant: 0))
    addConstraint(NSLayoutConstraint(item: titleLabel,
                                     attribute: .trailing,
                                     relatedBy: .equal,
                                     toItem: self,
                                     attribute: .trailing,
                                     multiplier: 1,
                                     constant: -10))
    addConstraint(NSLayoutConstraint(item: notesLabel,
                                     attribute: .trailing,
                                     relatedBy: .equal,
                                     toItem: self,
                                     attribute: .trailing,
                                     multiplier: 1,
                                     constant: -10))
    addConstraint(NSLayoutConstraint(item: notesLabel,
                                     attribute: .bottom,
                                     relatedBy: .equal,
                                     toItem: self,
                                     attribute: .bottom,
                                     multiplier: 1,
                                     constant: 0))
    addConstraint(NSLayoutConstraint(item: checkButton,
                                     attribute: .leading,
                                     relatedBy: .equal,
                                     toItem: self,
                                     attribute: .leading,
                                     multiplier: 1,
                                     constant: 20))
    addConstraint(NSLayoutConstraint(item: checkButton,
                                     attribute: .centerY,
                                     relatedBy: .equal,
                                     toItem: self,
                                     attribute: .centerY,
                                     multiplier: 1,
                                     constant: 0))
    addConstraint(NSLayoutConstraint(item: checkButton,
                                     attribute: .height,
                                     relatedBy: .equal,
                                     toItem: nil,
                                     attribute: .notAnAttribute,
                                     multiplier: 0,
                                     constant: 16))
    addConstraint(NSLayoutConstraint(item: checkButton,
                                     attribute: .width,
                                     relatedBy: .equal,
                                     toItem: nil,
                                     attribute: .notAnAttribute,
                                     multiplier: 0,
                                     constant: 16))
    addConstraint(NSLayoutConstraint(item: notesLabel,
                                     attribute: .leading,
                                     relatedBy: .equal,
                                     toItem: checkButton,
                                     attribute: .trailing,
                                     multiplier: 1,
                                     constant: 12))
    addConstraint(NSLayoutConstraint(item: titleLabel,
                                     attribute: .leading,
                                     relatedBy: .equal,
                                     toItem: checkButton,
                                     attribute: .trailing,
                                     multiplier: 1,
                                     constant: 12))
}

Upvotes: 1

Related Questions