21

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?

Community
  • 1
  • 1
orkenstein
  • 2,810
  • 3
  • 24
  • 45
  • You still need to use `tableView:heightForRowAtIndexPath:`. That's not an auto-layout job. – Daniyar Nov 21 '14 at 14:30
  • @Astoria, looks very much like auto-layout job. – orkenstein Feb 13 '15 at 22:36
  • @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 Feb 13 '15 at 23:32
  • @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 Feb 14 '15 at 08:05
  • 1
    @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](https://github.com/truppelito/SwiftUtils) (ExpandableTableViewCell and the sample project). It is still very much work in progress, though (but it works). – Alex Feb 14 '15 at 15:06
  • @Alex, cool, i'm interested – orkenstein Feb 14 '15 at 15:07
  • @Alex, not very good in swift. Could you please briefly post the idea as answer? – orkenstein Feb 14 '15 at 15:15

2 Answers2

48

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).

tounaobun
  • 14,570
  • 9
  • 53
  • 75
Alex
  • 5,009
  • 3
  • 39
  • 73
  • In non-animated version: wouldn't calling `reloadRowsAtIndexPaths:withRowAnimation:` with `UITableViewRowAnimationNone` on the table view work? Instead of disabling animations on `UIView` and `beginUpdates` / `endUpdates`. – Michał Ciuba Feb 14 '15 at 16:22
  • @MichałCiuba I don't remember exactly trying that out, but I do remember thinking: "It seems that `reloadRowsAtIndexPaths:withRowAnimation:` doesn't reload cell heights. I guess I'll have to try something else" (it might have been for animating the height, though, not an instantaneous update). – Alex Feb 14 '15 at 16:36
  • 1
    Yeah, I meant: call `layoutIfNeeded` first to update the cell's height, then `reloadRowsAtIndexPaths:withRowAnimation:` to update the table view. – Michał Ciuba Feb 14 '15 at 16:38
  • While this is good, be advised this only animates to the final state of the animation, not in between. – TigerCoding May 15 '16 at 04:22
  • I want to stress the `[tableView beginUpdates]; [tableView endUpdates];` is very important, without you'll get constraint-errors in your console that don't make much sense. – Kevin R Aug 01 '16 at 12:32
  • Hmm - that link is dead @Alex: https://github.com/truppelito/SwiftUtils – 10623169 Aug 11 '21 at 10:22
3

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
andrei
  • 1,353
  • 15
  • 24