Objective c iOS 10中的UITableViewCell动画高度问题

Objective c iOS 10中的UITableViewCell动画高度问题,objective-c,ios10,Objective C,Ios10,我的UITableViewCell将在识别轻触时设置其高度的动画。在iOS 9及以下版本中,此动画平滑且工作正常。在iOS 10 beta版中,动画过程中会出现不和谐的跳跃。有办法解决这个问题吗 下面是代码的一个基本示例 - (void)cellTapped { [self.tableView beginUpdates]; [self.tableView endUpdates]; } - (CGFloat)tableView:(UITableView *)tableView h

我的
UITableViewCell
将在识别轻触时设置其高度的动画。在iOS 9及以下版本中,此动画平滑且工作正常。在iOS 10 beta版中,动画过程中会出现不和谐的跳跃。有办法解决这个问题吗

下面是代码的一个基本示例

- (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

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


例如,如果将视图约束到单元格底部,它将跳转。该解决方案将涉及使用动态常数对顶部的约束。或者考虑另一种方法来定位视图,而不是与底部相关。

更好的解决方案:

问题是当
UITableView
更改单元格高度时,很可能是从
-beginUpdates
-endUpdates
更改单元格高度。在iOS 10之前,
大小
原点
都会在单元格上产生动画。现在,在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测试版接近完成,这个问题有望在下一版本中得到解决。还有一个开放的bug报告

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

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

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

就这样!无需应用
动画。持续时间
,因为表视图的
-beginUpdates
-endUpdates
会覆盖它。通常,如果您使用此技巧作为应用动画的无障碍方式,您将需要添加一个
动画.duration
,还可能添加一个
动画.timingFunction

我不使用自动布局,而是从UITableViewCell派生一个类,并使用其“LayoutSubView”以编程方式设置子视图的帧。因此,我试图修改CNOTETEGR8的答案以满足我的需要,并得出以下结论

重新实现单元的“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

这就成功了:)

Swift
中使用
AutoLayout
ios10
解决方案:

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

override func layoutSubviews() {
    super.layoutSubviews()

    if #available(iOS 10, *) {
        UIView.animateWithDuration(0.3) { self.contentView.layoutIfNeeded() }
    }
}
在目标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()
}

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

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


因此,对于使用布局约束的
UITableViewCell
和使用自动单元格高度的
UITableView
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()
}
请注意,
layoutifneed()
必须在
beginUpdates
/
endUpdates


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

由于声誉问题,我无法发表评论,但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是添加到单元格内容视图的子视图上的高度约束。

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

这不知怎么解决了它。尝试这两种解决方案,看看什么适合你。希望这能有所帮助。

哇,真是先进的东西。它对我起到了一半的作用:我的视图首先跳到不同的位置,但大小设置正确。看起来我的“边界”和“中心”发生了变化,后者没有设置动画。我尝试使用CAAnimationGroup进行self.layer.actions,但没有成功。。。你知道怎么处理吗?还有一个问题:那么这段代码需要添加到contentView的每个子视图中,这些子视图应该被设置动画?我无法获得像iOS9中那样的精确动画,iOS10中有很多错误,因此,由于此解决方案显著减少了跳跃,因此10仍然是
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)