iOS 10中的UITableViewCell动画高度问题

时间:2016-08-23 15:10:08

标签: objective-c ios10

我的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;
}

编辑:9/8/16

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

例如,如果我有一个约束到单元格底部的视图,它将跳转。解决方案将涉及使用动态常量对顶部的约束。或者想一想另一种方法来定位你的观点,然后再与底层相关。

7 个答案:

答案 0 :(得分:12)

iOS 10Swift AutoLayout的解决方案UITableViewCell

将此代码放入自定义override func layoutSubviews() { super.layoutSubviews() if #available(iOS 10, *) { UIView.animateWithDuration(0.3) { self.contentView.layoutIfNeeded() } } }

- (void)layoutSubviews
{
    [super layoutSubviews];

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

在Objective-C中:

UITableViewCell

如果您已将UITableViewDelegate配置如下,则会为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() } 更改高度设置动画:

interface IStylePreset {
    type : string;
    backgroundColor : string;
    color? : string
}

答案 1 :(得分:8)

更好的解决方案:

问题是当UITableView更改单元格的高度时,很可能是-beginUpdates-endUpdates。在iOS 10之前,sizeorigin都会在单元格上生成动画。现在,在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 beta的接近完成,希望此问题将在下一版本中得到解决。还有一个开放的错误报告。

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

第一步是调整图层检测到更改时发生的情况。此代码必须位于视图的子类中。该观点必须是您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

答案 2 :(得分:6)

我不使用自动布局,而是从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

这就是诀窍:)。

答案 3 :(得分:1)

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

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

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

之后出现

我认为这最后一部分可能对你有帮助....

答案 4 :(得分:0)

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

使用AutoLayout定位的Cell的子视图,因此我使用 UITableViewAutomaticDimension 进行高度计算(您可以尝试将其设置为tableView&#39; s 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;
}

答案 5 :(得分:0)

由于声誉无法发表评论,但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的子视图的高度约束。

答案 6 :(得分:0)

快速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)

以某种方式解决此问题。尝试两种解决方案,看看哪种对您有用。希望这会有所帮助。