【发布时间】:2014-10-21 02:35:27
【问题描述】:
考虑一个标准的垂直滚动流布局,其中填充了足够的单元格以引起滚动。当滚动到底部时,如果您删除一个项目,使得集合视图的内容大小必须缩小以容纳新的项目数量(即删除底部行的最后一个项目),从滚动的单元格行顶部被隐藏。在删除动画结束时,顶行出现没有动画——这是一个非常不愉快的效果。
慢动作:
复制真的很简单:
创建一个新的单视图项目并将默认的
ViewController更改为UICollectionViewController的子类将
UICollectionViewController添加到使用标准流程布局的情节提要中,并将其类更改为ViewController。为单元原型指定标识符“Cell”,大小为 200x200。将以下代码添加到
ViewController.m:
@interface ViewController ()
@property(nonatomic, assign) NSInteger numberOfItems;
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.numberOfItems = 19;
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return self.numberOfItems;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
return [collectionView dequeueReusableCellWithReuseIdentifier:@"Cell" forIndexPath:indexPath];
}
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
self.numberOfItems--;
[collectionView deleteItemsAtIndexPaths:@[indexPath]];
}
@end
附加信息
我在处理集合视图时看到了这个问题的其他表现形式,只是上面的例子似乎最简单地说明了这个问题。 UICollectionView 在默认动画期间似乎进入了某种恐慌的瘫痪状态,并且在动画完成之前拒绝取消隐藏某些单元格。它甚至可以防止在隐藏单元格上手动调用cell.hidden = NO 产生影响(之后hidden 仍然是YES)。下拉到底层并在那里设置hidden 是可行的,前提是您可以获得对要取消隐藏的单元格的引用,这在处理尚未显示的单元格时非常重要。
-initialLayoutAttributesForAppearingItemAtIndexPath 在调用deleteItemsAtIndexPaths: 时为每个可见的项目调用,但不是为滚动到视图中的项目调用。可以通过在批处理更新块内立即调用reloadData 来解决此问题,这似乎使集合视图意识到顶行即将出现:
[collectionView deleteItemsAtIndexPaths:@[indexPath]];
[collectionView performBatchUpdates:^{
[collectionView reloadData];
} completion:nil];
但不幸的是,这不是我的选择。我正在尝试通过操作单元格层和动画来实现一些自定义动画计时,并且调用 reloadData 确实会导致不必要的布局回调,从而使事情变得混乱。
更新:一些调查
我在许多布局方法中添加了日志语句,并查看了一些堆栈帧以尝试找出问题所在。至关重要的是,我正在检查何时调用 layoutSubviews、何时集合视图要求来自布局对象 (layoutAttributesForElementsInRect:) 的布局属性以及何时在单元格上调用 applyLayoutAttributes:。
我希望看到这样的一系列方法:
// user taps cell (to delete it)
-deleteItemsAtIndexPaths:
-layoutAttributesForElementsInRect:
-finalLayoutAttributes...: // Called for the item being deleted
-finalLayoutAttributes...: // \__ Called for each index path visible
-initialLayoutAttributes...: // / when deletion started
-applyLayoutAttributes: // Called for the item being deleted, to apply final layout attributes
// collection view begins scrolling up
-layoutSubviews: // Called multiple times as the
-layoutAttributesForElementsInRect: // collection view scrolls
// ... for any new set of
// ... attributes returned:
-collectionView:cellForItemAtIndexPath:
-applyLayoutAttributes: // Sets the standard attributes for the new cell
// collection view finishes scrolling
大部分情况正在发生;视图滚动时正确触发布局,并且集合视图正确查询布局以获取要显示的单元格的属性。然而,collectionView:cellForItemAtIndexPath: 和相应的applyLayoutAttributes: 方法直到删除后才会被调用,当最后一次调用布局时,会导致隐藏单元格被分配其布局属性(设置hidden = NO)。
因此,尽管从布局对象接收到所有正确响应,但集合视图似乎设置了某种标志以在更新期间不更新单元格。 UICollectionView 上有一个从 layoutSubviews 内部调用的私有方法,它似乎负责刷新单元格的外观:_updateVisibleCellsNow:。这是在应用单元格起始属性之前最终要求数据源提供新单元格的地方,这似乎是故障点,因为它没有在应该调用的时候被调用。
此外,这似乎与更新动画有关,或者至少在插入/删除的持续时间内没有更新单元格。例如以下没有故障的工作:
- (void)addCell
{
NSIndexPath *indexPathToInsert = [NSIndexPath indexPathForItem:self.numberOfItems
inSection:0];
self.numberOfItems++;
[self.collectionView insertItemsAtIndexPaths:@[indexPathToInsert]];
[self.collectionView scrollToItemAtIndexPath:indexPathToInsert
atScrollPosition:UICollectionViewScrollPositionCenteredVertically
animated:YES];
}
如果在插入的单元格位于当前可见边界之外时调用上述方法插入单元格,则插入项时不使用动画,并且集合视图会滚动到它,正确地出列并在途中显示单元格。
问题出现在 iOS 7 和 iOS 8 beta 5 中。
【问题讨论】:
-
作为参考,another question 说明了在
[UIView animateWithDuration:...]中更改contentOffset时会出现此问题。 -
您能否通过在将其滚动到屏幕外(以及将顶行滚动到屏幕上)时将其设置为隐藏来躲避该问题,然后在它离开屏幕时将其删除?
-
@pbasdf 是的,这确实可以确保滚动到的单元格正确显示。但是假设我删除单元格
14,而滚动到底部 - 使用您建议的解决方法单元格14会消失,并且在填补空白之前会有延迟。这对于我自己的集合视图来说更成问题,其中消失的单元格依靠下面的单元格向上滑过它的顶部。我必须先滚动而不淡出/滑动已删除的单元格,然后然后执行删除。看起来很奇怪,但这是一个改进,谢谢。 -
您是否尝试过在您的
deleteItemsAtIndexPaths之后立即使用emptyperformBatchUpdates?它似乎适用于简单的情况.... -
问题 B:它没有解决在更新期间未应用布局属性的根本问题。最明显的影响是对粘性标题之类的东西 - 在更新期间,标题不会保持“卡住”!所以事实上,任何依赖于当前内容偏移的布局(另一个例子是封面流布局)仍然不会很好地响应。不幸的是,我的布局属于这一类(我一点也不苛刻,是不是……)。
标签: ios uicollectionview uicollectionviewcell uicollectionviewlayout