5
votes

I have a simple UITableViewCell subclass in which I have a titleLabel property (the cell has more views, but for the sake of showing the issue, I will only do one label as it also breaks).

Here is my label code:

    self.titleLabel = UILabel(frame: .zero)
    self.titleLabel.numberOfLines = 0
    self.titleLabel.font = UIFont.preferredFont(forTextStyle: .headline)
    self.titleLabel.textColor = UIColor.white
    self.titleLabel.adjustsFontSizeToFitWidth = false
    self.titleLabel.textAlignment = .left
    self.titleLabel.translatesAutoresizingMaskIntoConstraints = false
    self.contentView.addSubview(self.titleLabel)

    self.titleLabel.topAnchor.constraint(equalTo: self.artworkImageView.topAnchor).isActive = true
    self.titleLabel.leftAnchor.constraint(equalTo: self.artworkImageView.rightAnchor, constant: 10.0).isActive = true
    self.titleLabel.rightAnchor.constraint(equalTo: self.contentView.rightAnchor, constant: -10.0).isActive = true
    self.titleLabel.bottomAnchor.constraint(equalTo: self.contentView.bottomAnchor).isActive = true

I also set my UITableView up like this:

self.tableView.rowHeight = UITableView.automaticDimension
self.tableView.estimatedRowHeight = 50.0

However it keeps breaking constraints with an error like this:

"<NSLayoutConstraint:0x28211ce10 'UIView-Encapsulated-Layout-Height' UITableViewCellContentView:0x10859a4f0.height == 4.33333   (active)>"

There are more constraints, however this one says that my cell content view is only 4.3 of height, however I want it to grow as the label grows.

I also tried setting contentHuggingPriorities and the priority of the bottom anchor. I also compared it to code online or IB constraints I saw online and they all set 4 constraints: top, left, bottom, right. I also tried leading and trailing instead of left and right - same result.

Any help appreciated

Here is my full AlbumTableViewCell:

class AlbumTableViewCell: UITableViewCell {

    public private(set) var artworkImageView: UIImageView
    public private(set) var titleLabel: UILabel
    public private(set) var albumInfoLabel: UILabel
    public private(set) var artistNameLabel: UILabel

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

        self.artworkImageView = UIImageView(frame: .zero)
        self.titleLabel = UILabel(frame: .zero)
        self.albumInfoLabel = UILabel(frame: .zero)
        self.artistNameLabel = UILabel(frame: .zero)

        super.init(style: style, reuseIdentifier: reuseIdentifier)

        self.tintColor = UIColor.white
        self.backgroundColor = UIColor.clear

        self.contentView.backgroundColor = UIColor.barTintColor
        self.contentView.layer.masksToBounds = false
        self.contentView.layer.cornerRadius = 10.0

        self.artworkImageView.layer.cornerRadius = 10.0
        self.artworkImageView.layer.masksToBounds = true
        self.artworkImageView.contentMode = .scaleAspectFit
        self.artworkImageView.translatesAutoresizingMaskIntoConstraints = false
        self.contentView.addSubview(self.artworkImageView)

        // image view
        self.artworkImageView.leadingAnchor.constraint(equalTo: self.contentView.leadingAnchor, constant: 5).isActive = true
        self.artworkImageView.topAnchor.constraint(equalTo: self.contentView.topAnchor, constant: 5).isActive = true
        self.artworkImageView.widthAnchor.constraint(equalToConstant: 80).isActive = true
        self.artworkImageView.heightAnchor.constraint(equalToConstant: 80).isActive = true

        self.titleLabel = UILabel(frame: .zero)
        self.titleLabel.numberOfLines = 2
        self.titleLabel.font = UIFont.preferredFont(forTextStyle: .headline)
        self.titleLabel.textColor = UIColor.white
        self.titleLabel.adjustsFontSizeToFitWidth = false
        self.titleLabel.textAlignment = .left
        self.titleLabel.translatesAutoresizingMaskIntoConstraints = false
        self.contentView.addSubview(self.titleLabel)

        // title
        self.titleLabel.leadingAnchor.constraint(equalTo: self.artworkImageView.trailingAnchor, constant: 5.0).isActive = true
        self.titleLabel.topAnchor.constraint(equalTo: self.contentView.topAnchor, constant: 5.0).isActive = true
        self.titleLabel.trailingAnchor.constraint(equalTo: self.contentView.trailingAnchor, constant: -5.0).isActive = true
        self.titleLabel.heightAnchor.constraint(equalToConstant: 35).isActive = true

        self.albumInfoLabel.numberOfLines = 1
        self.albumInfoLabel.font = UIFont.preferredFont(forTextStyle: .subheadline)
        self.albumInfoLabel.textColor = UIColor.lightGray
        self.albumInfoLabel.adjustsFontSizeToFitWidth = true
        self.albumInfoLabel.textAlignment = .left
        self.albumInfoLabel.translatesAutoresizingMaskIntoConstraints = false
        self.contentView.addSubview(self.albumInfoLabel)

        // albumInfoLabel
        self.albumInfoLabel.topAnchor.constraint(equalTo: self.titleLabel.bottomAnchor, constant: 5.0).isActive = true
        self.albumInfoLabel.leadingAnchor.constraint(equalTo: self.titleLabel.leadingAnchor).isActive = true
        self.albumInfoLabel.trailingAnchor.constraint(equalTo: self.titleLabel.trailingAnchor).isActive = true
        self.albumInfoLabel.heightAnchor.constraint(equalToConstant: 35).isActive = true

        self.artistNameLabel = UILabel(frame: .zero)
        self.artistNameLabel.numberOfLines = 1
        self.artistNameLabel.font = UIFont.preferredFont(forTextStyle: .subheadline)
        self.artistNameLabel.textColor = UIColor.lightGray
        self.artistNameLabel.adjustsFontSizeToFitWidth = true
        self.artistNameLabel.textAlignment = .left
        self.artistNameLabel.translatesAutoresizingMaskIntoConstraints = false
        self.contentView.addSubview(self.artistNameLabel)

        // albumInfoLabel
        self.artistNameLabel.topAnchor.constraint(equalTo: self.albumInfoLabel.bottomAnchor, constant: 5.0).isActive = true
        self.artistNameLabel.leadingAnchor.constraint(equalTo: self.albumInfoLabel.leadingAnchor).isActive = true
        self.artistNameLabel.trailingAnchor.constraint(equalTo: self.albumInfoLabel.trailingAnchor).isActive = true
        self.artistNameLabel.heightAnchor.constraint(equalToConstant: 35).isActive = true

        let selectedView: UIView = UIView(frame: .zero)
        selectedView.backgroundColor = UIColor.gray
        selectedView.layer.cornerRadius = 10.0
        selectedView.layer.masksToBounds = false
        self.selectedBackgroundView = selectedView
    }

    override func layoutSubviews() {
        super.layoutSubviews()

        let contentViewFrame = self.contentView.frame
        let insetContentViewFrame = contentViewFrame.inset(by: UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10))
        self.contentView.frame = insetContentViewFrame

        self.selectedBackgroundView?.frame = insetContentViewFrame
    }

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

This code does not crash anymore but the cell does not autoresize (see image).example image. The light gray area is the content view

this code does not break any constrains anymore, but the cell also does not calculate the hight automatically. Here is my table view controller:

self.tableView.register(AlbumTableViewCell.self, forCellReuseIdentifier: "AlbumCell")
self.tableView.separatorStyle = .none
self.tableView.tableFooterView = UIView(frame: .zero)
self.tableView.rowHeight = UITableView.automaticDimension
self.tableView.estimatedRowHeight = 50.0
3
"(the cell has more views, but for the sake of showing the issue, I will only do one label as it also breaks)" The breakage can be because of any of the other constraints in the contentView. So, it is important you share all of them or create a Minimal, Complete, and Verifiable Example. - Rakesha Shastri
No, I even tested the above code ( I commented out the other views) so it just that. This label alone breaks with the code above - Janosch Hübner
If you comment out the other code, your first two constraints will give you an error. - Rakesha Shastri
@JanoschHübner share a demo of the problem - Sh_Khan
Without looking at other UI elements of your table view cell, it's quite unlikely that anyone might be of help. If you are afraid that there is a lot of code, then you should make a demo that mimics your problem and upload it somewhere and attach the link in your question. - nayem

3 Answers

0
votes
        var titleLabel = UILabel()

        contentView.addSubview(titleLabel)
        titleLabel.textColor = UIColor(red:0.32, green:0.17, blue:0.12, alpha:1.0)
        titleLabel.font = UIFont.boldSystemFont(ofSize: 16.0)
        titleLabel.translatesAutoresizingMaskIntoConstraints = false
        titleLabel.topAnchor.constraint(equalTo: marginGuide.topAnchor).isActive = true
        titleLabel.trailingAnchor.constraint(equalTo: marginGuide.trailingAnchor, constant: 8).isActive = true
        titleLabel.bottomAnchor.constraint(equalTo: marginGuide.bottomAnchor).isActive = true
        titleLabel.leadingAnchor.constraint(equalTo: marginGuide.leadingAnchor, constant: 8).isActive = true

Try this.

0
votes

To be honest i don't know exactly what's your issue but i know for sure that you have a bad time with cell constraint and you want a dynamic cell .

So let's say you have a cell with 4 views artWrokImageView , artNameLabel , artDescriptionLabel and the artistNameLabel

First you need to make sure these views most constraint from top and bottom the table cell , So when you call self.tableView.rowHeight = UITableView.automaticDimension it knows how to dynamically expand .

Second you need to tell the table to expand when ever view did appear

This is demo for the 4 views above .

Table View Controller :

class YourTableViewController : UITableViewController {

        let customTableCellID = "customTableCellID";

        override func viewDidLoad() {
            super.viewDidLoad();
            setupTable();
        }

        override func viewWillAppear(_ animated: Bool) {
            super.viewWillAppear(animated)
            self.tableView.estimatedRowHeight = 50;
            self.tableView.rowHeight = UITableView.automaticDimension;
        }

        fileprivate func setupTable() {
            tableView.register(YourCustomTableCell.self, forCellReuseIdentifier: customTableCellID);
        }
    }
    extension YourTableViewController {

        override func numberOfSections(in tableView: UITableView) -> Int {
            return 1;
        }
        override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            return 1;
        }

        override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            let cell = tableView.dequeueReusableCell(withIdentifier: customTableCellID, for: indexPath) as! YourCustomTableCell
            cell.artistNameLabel.text = "the real art";
            cell.artworkImageView.image = UIImage(named: "mazen");
            cell.artDescriptionLabel.text = "long long long long long long long long long long long long long long long long long long long long long long long long long description";
            cell.artNameLabel.text = "someting"
            return cell
        }
    }

Cell :

class YourCustomTableCell : UITableViewCell {


    var artworkImageView : UIImageView = {
        let imageView = UIImageView();
        imageView.translatesAutoresizingMaskIntoConstraints = false;
        return imageView;
    }()
    var artNameLabel : UILabel = {
        let label = UILabel();
        label.font = UIFont.boldSystemFont(ofSize: 20);
        label.translatesAutoresizingMaskIntoConstraints = false;
        return label
    }()
    var artDescriptionLabel : UILabel = {
        let label = UILabel();
        label.textColor = .darkGray;
        label.numberOfLines = 0;
        label.translatesAutoresizingMaskIntoConstraints = false;
        return label;
    }()
    var artistNameLabel : UILabel = {
        let label = UILabel();
        label.textColor = .blue;
        label.translatesAutoresizingMaskIntoConstraints = false;
        return label;
    }()


    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier);
        setupCell();
    }
    fileprivate func setupCell() {

        // add views
        contentView.addSubview(artworkImageView);
        contentView.addSubview(artNameLabel);
        contentView.addSubview(artDescriptionLabel);
        contentView.addSubview(artistNameLabel);

        // layout views

        // image view
        artworkImageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 5).isActive = true;
        artworkImageView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 5).isActive = true;
        artworkImageView.widthAnchor.constraint(equalToConstant: 80).isActive = true;
        artworkImageView.heightAnchor.constraint(equalToConstant: 80).isActive = true;

        // art name
        artNameLabel.leadingAnchor.constraint(equalTo: artworkImageView.trailingAnchor, constant: 5).isActive = true;
        artNameLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 5).isActive = true;
        artNameLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -5).isActive = true;
        artNameLabel.heightAnchor.constraint(equalToConstant: 35).isActive = true;

        // descripion
        artDescriptionLabel.leadingAnchor.constraint(equalTo: artworkImageView.trailingAnchor, constant: 5).isActive = true;
        artDescriptionLabel.topAnchor.constraint(equalTo: artNameLabel.bottomAnchor, constant: 5).isActive = true;
        artDescriptionLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -5).isActive = true;
        // in art description label don't set the height anchors so it can expand

        // artist name
        artistNameLabel.leadingAnchor.constraint(equalTo: artworkImageView.trailingAnchor, constant: 5).isActive = true;
        artistNameLabel.topAnchor.constraint(equalTo: artDescriptionLabel.bottomAnchor, constant: 5).isActive = true;
        artistNameLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -5).isActive = true;
        artistNameLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -5).isActive = true; // this constraint is requierd for dynamic cell

    }

    required init?(coder aDecoder: NSCoder) {
        fatalError()
    }
}

And if this answer not in your case please tell me .

0
votes

Your tableviewcells don't know what their height is suppose to be. Which is fine.

You just have to give enough constraints so that it can figure it out. You're not doing that!

The major issue I currently see is that the artworkImageView is not constrained to top and bottom. Every vertical axiom of views needs to be constrained top to bottom.

Your first vertical axiom is just the image. It doesn't have a bottom constraint. Add that. So the tableviewcell knows how much it needs to resize itself. I strongly recommend you to see this moment of WWDC.

enter image description here

Also earlier in the same video at this moment it strongly recommend that you just dump your views in multiple stackviews and organize it that way. So that's also an alternative.

PS:

  1. don't dump self. It just increases the line width with no added benefit.

  2. Move all your non-layout related setup of your labels/images to their own instantiation. e.g.

lazy label : UILabel = {
   let label = UILabel()
   label.text = "John"
   label.translatesAutoresizingMaskIntoConstraints = false
   return label
}()
  1. No need to mention frame : .zero. Just UILabel() implies the frame is zero.
  2. It's always best to start your UI small then keep adding more elements to it. That way debugging your layout becomes easier/smaller.