Search code examples
iosswiftuitableviewautolayoutuilabel

UITableViewCell not expanding to fit items fully in content view


I am trying to layout a UITableViewCell that contains a UIImageView and UILabel.

I have configured my table view with

    tableView.estimatedRowHeight = 44
    tableView.rowHeight = UITableView.automaticDimension

I am adding my views to the table view content view. The label is being cut off however, it is a single line label.

cutoffcell

import UIKit

final class FeedImageItem: BaseFeedItemCell {

  var onRetry: (() -> Void)?

  private(set) var itemImageView = configure(UIImageView(frame: .zero), using: {
    $0.translatesAutoresizingMaskIntoConstraints = false
  })

  private(set) var publishedAtLabel = configure(UILabel(frame: .zero), using: {
    $0.translatesAutoresizingMaskIntoConstraints = false
  })

  private(set) var itemTitleLabel = UILabel(frame: .zero)

  private(set) var retryButton: UIButton = {
    let button = UIButton(type: .system)
    button.setTitle("reload", for: .normal)
    return button
  }()

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

    configureBindings()
    configureUI()
  }

  required init?(coder: NSCoder) {
    return nil
  }
}

private extension FeedImageItem {
  @objc func retryButtonTapped() {
    onRetry?()
  }

  func configureBindings() {
    retryButton.addTarget(self, action: #selector(retryButtonTapped), for: .touchUpInside)
  }

  func configureUI() {

    [itemImageView, publishedAtLabel].forEach(contentView.addSubview(_:))

    NSLayoutConstraint.activate([
      itemImageView.topAnchor.constraint(equalTo: contentView.topAnchor),
      itemImageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
      itemImageView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
      itemImageView.heightAnchor.constraint(equalToConstant: 184),

      publishedAtLabel.topAnchor.constraint(equalTo: itemImageView.bottomAnchor, constant: 8),
      publishedAtLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 8),
      publishedAtLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -8),
      publishedAtLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -8),
    ])

  }
}

How can I add items to my content view and have the cell expand with respect to the auto layout anchors I have added?

If I add my items to the view directly, I don't have this issue, but I was under the impression I should be adding to the content view.

My content view looks like the following:

open class BaseFeedItemCell: UITableViewCell {

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

  required public init?(coder: NSCoder) {
    return nil
  }

  open override func layoutSubviews() {
    super.layoutSubviews()

    contentView.frame = contentView.frame.inset(by: .init(top: 8, left: 12, bottom: 8, right: 12))

    contentView.backgroundColor = .white
    contentView.layer.cornerRadius = 12
    contentView.layer.shadowOffset = .init(width: 0, height: 4)
    contentView.layer.shadowColor = UIColor(red: 0.18, green: 0.18, blue: 0.18, alpha: 0.16).cgColor
    contentView.layer.shadowRadius = 4
    contentView.layer.shadowOpacity = 1

    let shadowFrame = contentView.layer.bounds
    let shadowPath = UIBezierPath(rect: shadowFrame).cgPath
    contentView.layer.shadowPath = shadowPath

    contentView.clipsToBounds = true
  }
}

private extension BaseFeedItemCell {
  func configureUI() {
    backgroundColor = .clear
  }
}

By setting a height on my label - publishedAtLabel.heightAnchor.constraint(greaterThanOrEqualToConstant: 44)

I get auto layout issues also.

(
    "<NSLayoutConstraint:0x600003671220 V:|-(0)-[UIImageView:0x7fee33523000]   (active, names: '|':UITableViewCellContentView:0x7fee335bd2b0 )>",
    "<NSLayoutConstraint:0x600003671310 UIImageView:0x7fee33523000.height == 184   (active)>",
    "<NSLayoutConstraint:0x600003671360 V:[UIImageView:0x7fee33523000]-(8)-[UILabel:0x7fee335bc100'Published 16 May 2020']   (active)>",
    "<NSLayoutConstraint:0x600003671400 UILabel:0x7fee335bc100'Published 16 May 2020'.bottom == UITableViewCellContentView:0x7fee335bd2b0.bottom - 8   (active)>",
    "<NSLayoutConstraint:0x6000036714a0 UILabel:0x7fee335bc100'Published 16 May 2020'.height >= 44   (active)>",
    "<NSLayoutConstraint:0x60000366df90 'UIView-Encapsulated-Layout-Height' UITableViewCellContentView:0x7fee335bd2b0.height == 228   (active)>"
)

Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0x6000036714a0 UILabel:0x7fee335bc100'Published 16 May 2020'.height >= 44   (active)>

Solution

  • Seems you are adding and constraining your label to the contentView. But, the contentView behaviour is handled by UIKit. Each time you do this you need to be aware of what's going on exactly with the contentView and what it's used for. Here's the documentation link to contentView. From your code, all I could gather is that the contentView constraints needs to be set to the UITableViewCell. Don't forget to set translatesAutoresizingMaskIntoConstraints to false and since it's already a subview you don't have to add it again. In my opinion, it's better to build out a custom contentView and constraint all the sides to the UITableViewCell. Hope this helps you.