2

I have a UITableView with custom UITableViewCells, each has a UIButton inside. I'm setting buttons' titles from an array, so the size of the buttons change according to the title. I need to return correct height based on the inner button's size in heightForRowAtIndexPath event.

Since I'm using auto layout, I've created an outlet for the button's height constraint and I'm updating it in the cell's layoutSubviews() event like this:

class CustomCell: UITableViewCell {

    /* ... */

    override func layoutSubviews() {
        super.layoutSubviews()
        self.myButton?.layoutIfNeeded()
        self.heightConstraint?.constant = self.myButton!.titleLabel!.frame.size.height
    }
}

Then I return the height based on the button height and top-bottom margins like so:

func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
    var cell = tableView.dequeueReusableCellWithIdentifier("CustomCell") as! CustomCell
    cell.myButton?.setTitle(self.data[indexPath.row], forState: UIControlState.Normal)
    cell.bounds = CGRectMake(0, 0, CGRectGetWidth(tableView.bounds), CGRectGetHeight(cell.bounds))
    cell.setNeedsLayout()
    cell.layoutIfNeeded()
    return cell.myButton!.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height + (cell.topMarginConstraint!.constant * 2) /* top-bottom margins */ + 1 /* separator height */
}

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    var cell = tableView.dequeueReusableCellWithIdentifier("CustomCell") as! CustomCell
    cell.myButton?.setTitle(self.data[indexPath.row], forState: UIControlState.Normal)
    return cell
}

On the first launch, there seems to be no problem. However, after I begin scrolling, then the height of some rows seem to be mistaken. When I get back to the top, I see that previous cell heights get to be broken as well.

When I googled for similar problems, issue seems to be about reusable cells, though I was unable to find another way to calculate the height. What can be done to reuse cells correctly or getting the correct height, perhaps by another method?

More info and source code:

Constraints set by IB like this:

Constraints SS

Here's the cells on the first launch:

Correct cell heights SS

After some scrolling:

Broken cell heights SS

Full code of the project can be found on Github.

kubilay
  • 4,247
  • 5
  • 43
  • 65

3 Answers3

1

According to this

Configure tableView as

func configureTableView() {
  tableView.rowHeight = UITableViewAutomaticDimension
  tableView.estimatedRowHeight = 160.0
}
  1. Call it on your viewDidLoad method

  2. Than configure your uibutton height constraint to be greater then or equal.

  3. Override func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat where you can place your estimation height code

Doro
  • 2,395
  • 2
  • 12
  • 25
  • I have, under cellForRowAtIndexPath, nothing changes; under heightForRowAtIndexPath, EXC_BAD_ACCESS. – kubilay Jun 30 '15 at 07:36
  • @kubilay, try to set uibutton height constarint to greater than or equal and in viewDidLoad tableView.rowHeight = UITableViewAutomaticDimension – Doro Jun 30 '15 at 07:42
  • Tried, Less in number, but there still are broken heights. – kubilay Jun 30 '15 at 07:46
1

First off, it's better if you perform constraint updates in func updateConstraints() method of UIView. So instead of

override func layoutSubviews() {
        super.layoutSubviews()
        self.myButton?.layoutIfNeeded()
        self.heightConstraint?.constant = self.myButton!.titleLabel!.frame.size.height
}

I would do

override func updateConstraints() {
        self.myButton?.layoutIfNeeded()
        self.heightConstraint?.constant = self.myButton!.titleLabel!.frame.size.height
        super.updateConstraints()
}

Note that you should call the super implementation at the end, not at the start. Then you would call cell.setNeedsUpdateConstraints() to trigger a constraint update pass.

Also you should never directly manipulate the cell bounds the way you are doing in heightForRowAtIndePath: method, and even if you are completely sure that manipulating directly is what you want, you should manipulate cell.contentView's bounds, not the cell's bounds. If you are looking to adjust the cell height dynamically with respect to the dimensions of the content, you should use self sizing cells. If you need to support iOS 7, then this answer tells you how to achieve that behaviour with autolayout only (without touching the bounds etc).

To reiterate the answer, you should do:

func viewDidLoad() {
    self.dummyCell = CustomCell.init()
    // additional setup
}

func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
    self.dummyCell.myButton?.setTitle(self.data[indexPath.row], forState: UIControlState.Normal)
    self.dummyCell.layoutIfNeeded() // or self.dummyCell.setNeedsUpdateConstraints() if and only if the button text is changing in the cell
    return self.dummyCell.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height
}

Please know that the answer I linked to outlines a strategy to get the cell height via autolayout, so only writing the code changes I proposed won't be enough unless you set your constraints in a way that makes this solution work. Please refer to that answer for more information. Hope it helps!

Community
  • 1
  • 1
akaralar
  • 1,033
  • 1
  • 10
  • 26
0

First of all, remove the height constraint of button and bind it to top and bottom with cell.

Then, in your cell' height, calculate height of the text based on the width and font of button. This will make the cell's height dynamic and you wont need height constraint anymore.

Refer the link below to get the height of text: Adjust UILabel height to text

Hope it helps. If you need help further or understanding anything, let me know.. :)

Community
  • 1
  • 1
Tejvansh
  • 656
  • 4
  • 9