21
votes

There are lot of similar questions on Stack Overflow about UITableViewCell height animation, but nothing works for new iOS8 auto-layout driven table view. My issue:

Custom cell:

@property (weak, nonatomic) IBOutlet iCarousel *carouselView;
@property (weak, nonatomic) IBOutlet UIPageControl *pageControl;
@property (weak, nonatomic) IBOutlet UIButton *seeAllButton;
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *carouselHeightConstraint;

Note carouselHeightConstraint. This is height constraint for content view's subview (the only subview).

Height is set to 230.0f for example. On first load it looks like: enter image description here

Then, on See All action I want to expand cell, my code:

- (IBAction)seeAllButtonAction:(id)sender {
  
  BOOL vertical = !self.carouselCell.carouselView.vertical;
  self.carouselCell.carouselView.type = vertical ? iCarouselTypeCylinder : iCarouselTypeLinear;
  self.carouselCell.carouselView.vertical = vertical;

  [UIView transitionWithView:self.carouselCell.carouselView
                    duration:0.2
                     options:0
                  animations:^{
  
    self.carouselCell.carouselHeightConstraint.constant = vertical ? CGRectGetHeight(self.tableView.frame) : 230;
    [self.carouselCell setNeedsUpdateConstraints];
    [self.carouselCell updateConstraintsIfNeeded];
    [self.tableView beginUpdates];
    [self.tableView endUpdates];

                  completion:^(BOOL finished) {
                  }];
}

As you can see, I try to use this good old solution:
Can you animate a height change on a UITableViewCell when selected?

And the result:

enter image description here

My cell shrinks to 44.0f height.

Question:

Why this happens? I expect to see my cell smoothly expanded with magic of auto-layot.

Note:
I dont't want to use -tableView:heightForRowAtIndexPath:. It's auto-layout era, right?

2
You still need to use tableView:heightForRowAtIndexPath:. That's not an auto-layout job.Daniyar
@Astoria, looks very much like auto-layout job.orkenstein
@orkenstein I totally agree this should now be done with Auto-Layout. I'm trying to do basically the same thing: setting constraints on the cell's contentView and telling the table view to animate the height. However, I couldn't get it to work so far. Did you find a solution for this problem?Alex
@Alex, I'v ended up using tableView:heightForRowAtIndexPath:. It looks like the only solution now. Sad but true. Don't forget to update your constraints properly.orkenstein
@orkenstein I actually did manage to solve my problem after I posted that comment, and I used a pure auto-layout approach: no use of tableView:heightForRowAtIndexPath:. If you're still interested, you can check out my solution here (ExpandableTableViewCell and the sample project). It is still very much work in progress, though (but it works).Alex

2 Answers

48
votes

The following worked for me:

Preparation:

  1. On viewDidLoad tell the table view to use self-sizing cells:

    tableView.rowHeight = UITableViewAutomaticDimension;
    tableView.estimatedRowHeight = 44; // Some average height of your cells
    
  2. Add the constraints as you normally would, but add them to the cell's contentView!

Animate height changes:

Say you change a constraint's constant:

myConstraint.constant = newValue;

...or you add/remove constraints.

To animate this change, proceed as follows:

  1. Tell the contentView to animate the change:

    [UIView animateWithDuration: 0.3 animations: ^{ [cell.contentView layoutIfNeeded] }]; // Or self.contentView if you're doing this from your own cell subclass
    
  2. Then tell the table view to react to the height change with an animation

    [tableView beginUpdates];
    [tableView endUpdates];
    

The duration of 0.3 on the first step is what seems to be the duration UITableView uses for its animations when calling begin/endUpdates.

Bonus - change height without animation and without reloading the entire table:

If you want to do the same thing as above, but without an animation, then do this instead:

[cell.contentView layoutIfNeeded];
[UIView setAnimationsEnabled: FALSE];
[tableView beginUpdates];
[tableView endUpdates];
[UIView setAnimationsEnabled: TRUE];

Summary:

// Height changing code here:
// ...

if (animate) {
    [UIView animateWithDuration: 0.3 animations: ^{ [cell.contentView layoutIfNeeded]; }];
    [tableView beginUpdates];
    [tableView endUpdates];
}
else {
    [cell.contentView layoutIfNeeded];
    [UIView setAnimationsEnabled: FALSE];
    [tableView beginUpdates];
    [tableView endUpdates];
    [UIView setAnimationsEnabled: TRUE];
}

You can check out my implementation of a cell that expands when the user selects it here (pure Swift & pure autolayout - truly the way of the future).

3
votes

I would like to add a few points to Alex's answer.

If you call beginUpdates and endUpdates inside cellForRowAtIndexPath or willDisplayCell, your app will crash. After animating your cell's increase, you might want it stay the same after scrolling to your tableView. You might also want that the rest of the cells keep their height the same. But remember that cells are reusable, so you might end up with other cells having increased height.

As a result, you will have to set the constraint constant inside cellForRowAtIndexPath depending on the item it represents. But if you call layoutIfNeeded afterwards, it will display a warning with your constraints. My belief is that it attempts to generate the cell layout before its new height is computed.

public func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
...
    myConstraint.constant = itemForCell.value == "x" ? 50 : 20 // stop right here
    cell.layoutIfNeeded() <- don't do this or some of your constraints will break
    beginUpdates() <-- if you do this your app will crash
    endUpdates() <-- same here