4

I use Automatic Height for cells. And each time I want to update my cell with new data (model variable here) I updated my autolayout constraints and I get an error. Here just to show the issue, I don't even change the constraints. I simply ask to recalculate the layout.

At the first init of the cell : No warning, no problem.

The error:

<NSLayoutConstraint:0x174285d70 'UIView-Encapsulated-Layout-Height'
 UITableViewCellContentView:0x1030f8a50.height == 500   (active)>

The code:

tableView.rowHeight = UITableViewAutomaticDimension

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

class TestTableViewCell: UITableViewCell {

  var model: X? {
    didSet {
      setNeedsLayout()
      layoutIfNeeded()
    }
  }

    override func awakeFromNib() {
        super.awakeFromNib()
        // Initialization code
    }

    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)

        // Configure the view for the selected state
    }

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

    let viewtop = UIView()
    let viewbottom = UIView()

    viewtop.backgroundColor = UIColor.yellow
    viewbottom.backgroundColor = UIColor.red
    contentView.backgroundColor = UIColor.blue

    contentView.addSubview(viewtop)
    contentView.addSubview(viewbottom)

    viewtop.snp.makeConstraints { (make) in
      make.top.equalTo(contentView)
      make.left.right.equalTo(contentView)
      make.height.equalTo(50)
    }

    viewbottom.snp.makeConstraints { (make) in
      make.top.equalTo(viewtop.snp.bottom)
      make.left.right.equalTo(contentView)
      make.bottom.equalTo(contentView)
      make.height.equalTo(120)
    }
  }

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

}

The question: Why, after asking a re-layout of the same constraints, do I get an error ?

EDIT: another example for better comprehension here.

var botViewHeightConstraint:Constraint!
class TestTableViewCell: UITableViewCell {

  var model: Int? {
    didSet {
      if model == 1 {
        botViewHeightConstraint.update(offset:200)
      }else{
        botViewHeightConstraint.update(offset:120)
      }
    }
  }

    override func awakeFromNib() {
        super.awakeFromNib()
        // Initialization code
    }

    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)

        // Configure the view for the selected state
    }

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

    let viewtop = UIView()
    let viewbottom = UIView()

    viewtop.backgroundColor = UIColor.yellow
    viewbottom.backgroundColor = UIColor.red
    contentView.backgroundColor = UIColor.blue

    contentView.addSubview(viewtop)
    contentView.addSubview(viewbottom)

    viewtop.snp.makeConstraints { (make) in
      make.top.equalTo(contentView)
      make.left.right.equalTo(contentView)
      make.height.equalTo(50)
    }

    viewbottom.snp.makeConstraints { (make) in
      make.top.equalTo(viewtop.snp.bottom)
      make.left.right.equalTo(contentView)
      make.bottom.equalTo(contentView)
      botViewHeightConstraint = make.height.equalTo(120).constraint
    }
  }

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

}

CellForRow code:

 override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    if let post = fetchedResultsController?.fetchedObjects?[(indexPath as NSIndexPath).section]  {
       let cell = tableView.dequeueReusableCell(withIdentifier: imagePostCellId) as! TestTableViewCell!
       cell.model = 1
       return cell
    }
}
Mikael
  • 2,155
  • 1
  • 18
  • 42
  • You can copy paste my custom cell in a test project, you will see the warning. I'm using Snapkit for swift 3 through cocoapods. – Mikael Sep 23 '16 at 09:01

2 Answers2

5

First, you keep neglecting to set your subviews' translatesAutoresizingMaskIntoConstraints to false. That's important. (Perhaps snapkit does that for you, however. I don't know.)

Second — and this is the big point — what you're doing is not how you size a variable height cell from the inside out. You do not change an absolute height constraint, as your code is doing. The sizing is based ultimately on the intrinsic content size of the subviews. Some subviews might have absolute heights, certainly, but ultimately there must be at least one with an intrinsic content size. That is the size that you are able to change dynamically in order to determine the height of a cell.

That is why, for example, a cell containing a UILabel is so easy to use with dynamic row heights. It has an intrinsic content size.

The intrinsic content size does not conflict with the built-in height of the cell (the UIView-Encapsulated-Layout-Height in your console dump); it supplements it (when the runtime calls systemLayoutSizeFitting(UILayoutFittingCompressedSize) behind the scenes, which is how automatic variable row heights works).

If you use custom subviews with an implementation of intrinsicContentSize, and if setting your model value in the cell triggers a call to invalidateIntrinsicContentSize, your example will work perfectly with no complaints in the console.

Here is an example of such a custom view:

class MyView : UIView {
    var h : CGFloat = 200 {
        didSet {
            self.invalidateIntrinsicContentSize()
        }
    }
    override var intrinsicContentSize: CGSize {
        return CGSize(width:300, height:self.h)
    }
}

When this is a subview of your cell's content view, setting this view's h in cellForRow sizes the cell's height correctly.

For example, let's suppose our cell's content view has just one subview, v, which is a MyView. Then:

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! MyCell
    let even = indexPath.row % 2 == 0
    cell.v.backgroundColor = even ? .red : .green
    cell.v.h = even ? 40 : 80 // triggers layout!
    return cell
}
matt
  • 447,615
  • 74
  • 748
  • 977
  • As you requested, I've also posted this as a downloadable example project: https://github.com/mattneub/variableRowHeights Download the example and run it in Xcode 8. Scroll like crazy! No problem. The cells are alternately tall and short. – matt Sep 26 '16 at 14:26
  • First, you keep neglecting to set your subviews' translatesAutoresizingMaskIntoConstraints to false. That's important. (Perhaps snapkit does that for you, however. I don't know.) -> yes Snapkit does it for me – Mikael Sep 27 '16 at 07:30
  • Sorry Matt, my bad, I though by accepting your answer you would get the bounty automatically... – Mikael Oct 04 '16 at 06:36
2

Why are you adding "make.height.equalTo(120)" when you've already set top bottom left and right constrain relative to content and top view

As per your code it seems the cell height is always 50+120.

Also see if overriding heightForRow and return UITableViewAutomaticDimension works .

sashi_bhushan
  • 394
  • 3
  • 13
  • I m already setting UITableViewAutomaticDimension in heightForRow. I add the size + bottom + top because this is necessary for the cell to resize with autolayout. – Mikael Sep 26 '16 at 05:29
  • UITableViewAutomaticDimension if for layouting dynamically wrt constrains. – sashi_bhushan Sep 26 '16 at 06:35
  • If you cell is always going to be the same height , why are you at all using UITableViewAutomaticDimension here. It's will un-necessarily do layout passes when you already know the height to be. simply pass the constant height – sashi_bhushan Sep 26 '16 at 06:36
  • the reason is that the cell is not always the same height at all. In the sample I set fixed values such as if model == 1 { botViewHeightConstraint.update(offset:200) } but in my project the cell height actually depends on the textContent and images height. – Mikael Sep 26 '16 at 07:30