在识别水龙头时,我的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
的动画都不起作用。
例如,如果我有一个约束到单元格底部的视图,它将跳转。解决方案将涉及使用动态常量对顶部的约束。或者想一下另一种方法来定位你的观点,然后再与底层相关。
使用iOS 10
解决Swift
中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];
}];
}
}
如果您已经如下配置了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()
}
改善方案:
问题是当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 beta的接近完成,希望这个问题将在下一个版本中得到解决。还有一个开放的错误报告。
我的解决方案涉及到层的CAAnimation
。这将检测高度的变化并自动制作动画,就像使用未连接到CALayer
的UIView
一样。
第一步是调整图层检测到更改时发生的情况。此代码必须位于视图的子类中。这个观点必须是你的tableViewCell.contentView
的子视图。在代码中,我们检查视图的图层的actions属性是否具有动画的关键字。如果不只是打电话给超级。
- (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
。
我不使用自动布局,而是从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
这就是诀窍:)
所以我对使用布局约束的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()
}
请注意,qazxsw poi必须在qazxsw poi / qazxsw poi之后来
我认为这最后一部分可能对你有所帮助....
斯威夫特4 ^
当我在桌子底部并试图在我的tableView中展开和折叠一些行时出现跳转问题。我先尝试了这个解决方案:
layoutIfNeeded()
但是,当我尝试扩展行,向上滚动,然后突然崩溃时,问题再次出现。所以我尝试了这个:
beginUpdates
这以某种方式修复它。尝试两种解决方案,看看哪种方法适合您。希望这可以帮助。
面对iOS 10上的相同问题(在iOS 9上一切都很好),解决方案是通过UITableViewDelegate方法实现提供实际的estimatedRowHeight和heightForRow。
使用AutoLayout定位Cell的子视图,因此我使用UITableViewAutomaticDimension进行高度计算(您可以尝试将其设置为tableView的rowHeight属性)。
endUpdates
由于声誉无法发表评论,但Sam的解决方案在IOS 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
}
其中constraintToAnimate是添加到单元格contentView的子视图的高度约束。