如何使用自动布局实现UITableViewCell高度的动画效果?

21

在Stack Overflow上有很多关于UITableViewCell高度动画的类似问题,但是对于新的iOS8自动布局驱动的表格视图,没有任何有效的解决方法。我的问题是:

定制单元格:

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

请注意carouselHeightConstraint。这是内容视图子视图(唯一的子视图)的高度约束。

例如,高度设置为230.0f。首次加载时,它看起来像这样: enter image description here

然后,在查看全部操作中,我想要展开单元格,我的代码:

- (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) {
                  }];
}

正如您所看到的,我尝试使用这个老旧的解决方案:
当选择时,您能否对UITableViewCell执行高度更改的动画?

结果如下:

enter image description here

我的单元格缩小44.0f高度。

问题:

为什么会发生这种情况?我期望看到我的单元格在自动布局的魔力下平滑扩展。

注意:
我不想使用-tableView:heightForRowAtIndexPath:。这是自动布局时代,对吧?


你仍然需要使用 tableView:heightForRowAtIndexPath:。这不是自动布局的工作。 - Daniyar
@orkenstein,我完全同意现在应该使用自动布局来完成这个任务。我正在尝试做基本相同的事情:在单元格的contentView上设置约束,并告诉表视图动画高度。然而,到目前为止我还没有成功。你找到了解决这个问题的方法吗? - Alex
@Alex,我最终使用了tableView:heightForRowAtIndexPath:。现在看起来这是唯一的解决方案。悲伤但却是事实。不要忘记适当更新你的约束条件。 - orkenstein
1
@orkenstein,在我发表那条评论后,我实际上已经成功解决了我的问题,并且我采用了纯自动布局的方法:没有使用tableView:heightForRowAtIndexPath:。如果你仍然感兴趣,可以在这里查看我的解决方案(ExpandableTableViewCell和示例项目)。尽管它还有很多工作要做,但它已经可以正常运行了。 - Alex
@Alex,很酷,我很感兴趣。 - orkenstein
显示剩余2条评论
2个回答

48

以下方法适用于我:

准备工作:

  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!

动态更改高度:

假设你要更改一个约束的constant值:

myConstraint.constant = newValue;

...或者您添加/删除约束条件。

要动画显示此更改,请按以下步骤进行:

  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];
    

第一步中的0.3持续时间似乎是UITableView在调用begin/endUpdates时使用的动画持续时间。

Bonus - 在不重新加载整个表格且没有动画的情况下更改高度:

如果您想要做与上述相同的事情,但不需要动画,则可以使用以下方式:

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

摘要:

// 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];
}

你可以在这里查看我编写的当用户选择时会扩展的单元格的实现方式(这是纯Swift和纯自动布局 - 真正的未来方向)

非动画版本:在表视图上使用reloadRowsAtIndexPaths:withRowAnimation:并将其设置为UITableViewRowAnimationNone不就可以了吗?而不是禁用UIView上的动画和beginUpdates/endUpdates - Michał Ciuba
@MichałCiuba 我不记得确切地尝试过那个,但我记得当时想过:“似乎reloadRowsAtIndexPaths:withRowAnimation:不会重新加载单元格高度。我猜我得尝试其他方法”(可能是为了动画高度,而不是瞬间更新)。 - Alex
1
是的,我的意思是:先调用 layoutIfNeeded 来更新单元格的高度,然后再使用 reloadRowsAtIndexPaths:withRowAnimation: 来更新表视图。 - Michał Ciuba
虽然这很好,但请注意,这只会将动画呈现为最终状态,而不能在过程中显示。 - TigerCoding
我想强调[tableView beginUpdates]; [tableView endUpdates];非常重要,如果没有这两个方法,你的控制台会出现一些不太有意义的约束错误。 - Kevin R
嗯 - 那个链接已经失效了 @Alex: https://github.com/truppelito/SwiftUtils - 10623169

3
我想在Alex的回答中添加一些要点。
如果你在cellForRowAtIndexPath或willDisplayCell方法中调用beginUpdates和endUpdates,你的应用程序将崩溃。在动画显示增加行高之后,你可能希望在滚动tableView之后保持相同的行高。你可能还希望其他的单元格保持相同的高度。但是请记住,单元格是可重用的,所以你可能会得到其他单元格增加高度的情况。
因此,你需要根据每个单元格所表示的项目,在cellForRowAtIndexPath方法中设置约束常数。但是,如果之后调用layoutIfNeeded,它将显示带有您约束条件的警告。我的信念是它试图在计算新高度之前生成单元格布局。
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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接