【问题标题】:UITableViewCell animate height issue in iOS 10iOS 10 中的 UITableViewCell 动画高度问题
【发布时间】:2016-12-30 11:58:42
【问题描述】:

我的UITableViewCell 将在识别点击时为其高度设置动画。在 iOS 9 及更低版本中,此动画流畅且没有问题。在 iOS 10 测试版中,动画期间出现了不和谐的跳跃。有没有办法解决这个问题?

这是代码的基本示例。

- (void)cellTapped {
    [self.tableView beginUpdates];
    [self.tableView endUpdates];
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    return self.shouldExpandCell ? 200.0f : 100.0f;
}

编辑:2016 年 9 月 8 日

问题仍然存在于 GM 中。经过更多调试后,我发现问题与单元格立即跳转到新高度有关,然后将动画相关单元格偏移。这意味着任何依赖于单元格底部的基于CGRect 的动画都将不起作用。

例如,如果我有一个限制在单元格底部的视图,它会跳转。该解决方案将涉及使用动态常数对顶部进行约束。或者考虑另一种方式来定位您的视图,而不是与底部相关。

【问题讨论】:

    标签: objective-c ios10


    【解决方案1】:

    Swift 中的iOS 10 与AutoLayout 的解决方案:

    将此代码放入您的自定义UITableViewCell

    override func layoutSubviews() {
        super.layoutSubviews()
    
        if #available(iOS 10, *) {
            UIView.animateWithDuration(0.3) { self.contentView.layoutIfNeeded() }
        }
    }
    

    在 Objective-C 中:

    - (void)layoutSubviews
    {
        [super layoutSubviews];
    
        NSOperatingSystemVersion ios10 = (NSOperatingSystemVersion){10, 0, 0};
        if ([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:ios10]) {
            [UIView animateWithDuration:0.3
                             animations:^{
                                 [self.contentView layoutIfNeeded];
                             }];
        }
    }
    

    如果您已配置您的UITableViewDelegate,这将为UITableViewCell 更改高度设置动画,如下所示:

    func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
        return selectedIndexes.contains(indexPath.row) ? Constants.expandedTableViewCellHeight : Constants.collapsedTableViewCellHeight
    }
    
    func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
        tableView.deselectRowAtIndexPath(indexPath, animated: true)
        tableView.beginUpdates()
        if let index = selectedIndexes.indexOf(indexPath.row) {
            selectedIndexes.removeAtIndex(index)
        } else {
            selectedIndexes.append(indexPath.row)
        }
        tableView.endUpdates()
    }
    

    【讨论】:

    • 这个答案的第一部分对我来说是最好的解决方案!
    • 在layoutSubviews 内部调用layoutIfNeeded 会在最初加载和设置单元格时导致大量动画。我从下面的 Markus 那里得到答案,他在 setFrame 中调用 layoutIfNeeded 而不是 layoutSubviews。 stackoverflow.com/a/39329522/1570970
    • 谢谢兄弟,很棒的回答!
    【解决方案2】:

    更好的解决方案:

    问题是当UITableView 更改单元格的高度时,很可能是-beginUpdates 和-endUpdates。在 iOS 10 之前,size 和 origin 都会在单元格上播放动画。现在,在 iOS 10 GM 中,单元格将立即更改为新高度,然后将动画到正确的偏移量。

    解决方案非常简单,带有约束。创建一个引导约束,它将更新它的高度,并将需要约束到单元格底部的其他视图现在约束到这个引导。

    - (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
        self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
        if (self) {
            UIView *heightGuide = [[UIView alloc] init];
            heightGuide.translatesAutoresizingMaskIntoConstraints = NO;
            [self.contentView addSubview:heightGuide];
            [heightGuide addConstraint:({
                self.heightGuideConstraint = [NSLayoutConstraint constraintWithItem:heightGuide attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0f constant:0.0f];
            })];
            [self.contentView addConstraint:({
                [NSLayoutConstraint constraintWithItem:heightGuide attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.contentView attribute:NSLayoutAttributeTop multiplier:1.0f constant:0.0f];
            })];
    
            UIView *anotherView = [[UIView alloc] init];
            anotherView.translatesAutoresizingMaskIntoConstraints = NO;
            anotherView.backgroundColor = [UIColor redColor];
            [self.contentView addSubview:anotherView];
            [anotherView addConstraint:({
                [NSLayoutConstraint constraintWithItem:anotherView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0f constant:20.0f];
            })];
            [self.contentView addConstraint:({
                [NSLayoutConstraint constraintWithItem:anotherView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.contentView attribute:NSLayoutAttributeLeft multiplier:1.0f constant:0.0f];
            })];
            [self.contentView addConstraint:({
                // This is our constraint that used to be attached to self.contentView
                [NSLayoutConstraint constraintWithItem:anotherView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:heightGuide attribute:NSLayoutAttributeBottom multiplier:1.0f constant:0.0f];
            })];
            [self.contentView addConstraint:({
                [NSLayoutConstraint constraintWithItem:anotherView attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self.contentView attribute:NSLayoutAttributeRight multiplier:1.0f constant:0.0f];
            })];
        }
        return self;
    }
    

    然后在需要时更新参考线高度。

    - (void)setFrame:(CGRect)frame {
        [super setFrame:frame];
    
        if (self.window) {
            [UIView animateWithDuration:0.3 animations:^{
                self.heightGuideConstraint.constant = frame.size.height;
                [self.contentView layoutIfNeeded];
            }];
    
        } else {
            self.heightGuideConstraint.constant = frame.size.height;
        }
    }
    

    请注意,将更新指南放在 -setFrame: 中可能不是最佳位置。到目前为止,我只构建了这个演示代码来创建解决方案。使用最终解决方案完成代码更新后,如果找到更好的放置位置,我将进行编辑。

    原答案:

    随着 iOS 10 测试版接近完成,希望这个问题将在下一个版本中得到解决。还有一个开放的错误报告。

    我的解决方案涉及到图层的CAAnimation。这将检测高度的变化并自动设置动画,就像使用未链接到 UIView 的 CALayer 一样。

    第一步是调整图层检测到变化时发生的情况。此代码必须在您的视图的子类中。该视图必须是您的tableViewCell.contentView 的子视图。在代码中,我们检查视图层的动作属性是否有我们动画的键。如果不只是打电话给超级。

    - (id<CAAction>)actionForLayer:(CALayer *)layer forKey:(NSString *)event {
        return [layer.actions objectForKey:event] ?: [super actionForLayer:layer forKey:event];
    }
    

    接下来您要将动画添加到 actions 属性。您可能会发现此代码最好在视图位于窗口上并布局后应用。预先应用它可能会在视图移动到窗口时产生动画。

    - (void)didMoveToWindow {
        [super didMoveToWindow];
    
        if (self.window) {
            CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"bounds"];
            self.layer.actions = @{animation.keyPath:animation};
        }
    }
    

    就是这样!无需应用animation.duration,因为表格视图的-beginUpdates 和-endUpdates 会覆盖它。一般来说,如果您将此技巧用作应用动画的一种轻松方式,您将需要添加一个animation.duration,也许还需要添加一个animation.timingFunction。

    【讨论】:

    • 哇,真是高级的东西。它对我来说是一半:我的视图首先跳转到不同的位置,但大小是正确的动画。看起来我的“边界”和“中心”发生了变化,后者没有动画。我尝试为 self.layer.actions 使用 CAAnimationGroup,但它不起作用......知道如何处理它吗?
    • 还有一个问题:那么这个代码需要添加到contentView的每个应该动画的子视图中吗?
    • 我无法像在 iOS9 中那样获得精确的动画,有很多 iOS10 错误,所以由于这个解决方案显着减少了跳跃,并且 10 仍然是测试版,所以我没有进一步进行。但是,您可以尝试将其他动画添加到动作字典中。动画组与此代码无关。尝试为bounds.origin 和bounds.size 添加动画,如果不起作用,请尝试bounds 和position。
    • 现在这将为您的视图的键值设置动画(例如它的边界)。因此,如果您有此视图的子视图并且它们的布局设置正确,那么动画将在子视图上按预期工作。但是,如果您有多个需要动画的兄弟视图,那么您需要将代码添加到每个视图中。再说一次,我不会花太多时间在上面。希望这个动画错误将在其中一个 iOS10 GM 中得到修复。
    • 酷 - “边界”和“位置”成功了!我刚刚创建了两个动画并将它们放入字典中: self.layer.actions = @{animation1.keyPath:animation1, animation2.keyPath:animation2 };
    【解决方案3】:

    我不使用自动布局,而是从 UITableViewCell 派生一个类,并使用它的“layoutSubviews”以编程方式设置子视图的框架。所以我尝试修改 cnotethegr8 的答案以满足我的需要,我想出了以下内容。

    重新实现单元格的“setFrame”,将单元格的内容高度设置为单元格高度并触发动画块中子视图的重新布局:

    @interface MyTableViewCell : UITableViewCell
    ...
    @end
    
    @implementation MyTableViewCell
    
    ...
    
    - (void) setFrame:(CGRect)frame
    {
        [super setFrame:frame];
    
        if (self.window)
        {
            [UIView animateWithDuration:0.3 animations:^{
                self.contentView.frame = CGRectMake(
                                            self.contentView.frame.origin.x,
                                            self.contentView.frame.origin.y,
                                            self.contentView.frame.size.width,
                                            frame.size.height);
                [self setNeedsLayout];
                [self layoutIfNeeded];
            }];
        }
    }
    
    ...
    
    @end
    

    成功了 :)

    【讨论】:

    • 谢谢!这解决了我的问题。
    • 我基本上做了同样的事情,但不必设置self.contentView.frame
    【解决方案4】:

    因此,对于使用布局约束的 UITableViewCell 和使用自动单元格高度 (UITableViewAutomaticDimension) 的 UITableView,我遇到了这个问题。所以我不确定这个解决方案有多少适合你,但我把它放在这里,因为它是密切相关的。

    主要实现是降低导致高度变化发生的约束的优先级:

    self.collapsedConstraint = self.expandingContainer.topAnchor.constraintEqualToAnchor(self.bottomAnchor).priority(UILayoutPriorityDefaultLow)
    self.expandedConstraint = self.expandingContainer.bottomAnchor.constraintEqualToAnchor(self.bottomAnchor).priority(UILayoutPriorityDefaultLow)
    
    self.expandedConstraint?.active = self.expanded
    self.collapsedConstraint?.active = !self.expanded
    

    其次,我为 tableview 设置动画的方式非常具体:

    UIView.animateWithDuration(0.3) {
    
        let tableViewCell = tableView.cellForRowAtIndexPath(viewModel.index) as! MyTableViewCell
        tableViewCell.toggleExpandedConstraints()
        tableView.beginUpdates()
        tableView.endUpdates()
        tableViewCell.layoutIfNeeded()
    }
    

    请注意,layoutIfNeeded() 必须在 beginUpdates/endUpdates 之后出现

    我认为最后一部分可能对您有所帮助....

    【讨论】:

    • 这里绝对是一个被低估的答案。降低改变高度的约束的优先级对我来说有所不同。谢谢!
    【解决方案5】:

    斯威夫特 4^

    当我在表格底部并尝试在我的 tableView 中展开和折叠一些行时遇到了跳转问题。我首先尝试了这个解决方案:

    var cellHeights: [IndexPath: CGFloat] = [:]
    
    func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
        cellHeights[indexPath] = cell.frame.size.height
    }
    
    func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
        return cellHeights[indexPath] ?? UITableView.automaticDimension
    }
    

    但是,当我尝试展开该行,向上滚动,然后突然折叠该行时,问题再次出现。所以我尝试了这个:

    let savedContentOffset = contentOffset
    
    tableView.beginUpdates()
    tableView.endUpdates()
    tableView.layer.removeAllAnimations()
    
    tableView.setContentOffset(savedContentOffset, animated: false)
    

    这以某种方式解决了它。尝试这两种解决方案,看看哪种方法适合您。希望这会有所帮助。

    【讨论】:

      【解决方案6】:

      在 iOS 10 上遇到同样的问题(在 iOS 9 上一切正常),解决方案是通过 UITableViewDelegate 方法实现提供实际的 estimatedRowHeight 和 heightForRow。

      Cell 的子视图使用 AutoLayout 定位,因此我使用 UITableViewAutomaticDimension 进行高度计算(您可以尝试将其设置为 tableView 的 rowHeight 属性)。

      - (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {
      
          CGFloat result = kDefaultTableViewRowHeight;
      
          if (isAnimatingHeightCellIndexPAth) {
              result = [self precalculatedEstimatedHeight];
          }
      
          return result;
       }
      
      
      - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
      {
          return UITableViewAutomaticDimension;
      }
      

      【讨论】:

        【解决方案7】:

        由于声誉问题无法发表评论,但 sam 的解决方案在 IOS 10 上对我有用。

         - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
        
            EWGDownloadTableViewCell *cell = (EWGDownloadTableViewCell *)[tableView cellForRowAtIndexPath:indexPath];
        
            if (self.expandedRow == indexPath.row) {
                self.expandedRow = -1;
                [UIView animateWithDuration:0.3 animations:^{
                    cell.constraintToAnimate.constant = 51;
                    [tableView beginUpdates];
                    [tableView endUpdates];
                    [cell layoutIfNeeded];
                } completion:nil];
            } else {
                self.expandedRow = indexPath.row;
                [UIView animateWithDuration:0.3 animations:^{
                    cell.constraintToAnimate.constant = 100;
                    [tableView beginUpdates];
                    [tableView endUpdates];
                    [cell layoutIfNeeded];
                } completion:nil];
            } }
        

        其中 constraintToAnimate 是添加到单元格 contentView 的子视图的高度约束。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2014-12-15
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-09-02
          相关资源
          最近更新 更多