考虑一个标准的垂直滚动流布局,其中填充了足够的单元格以引起滚动。当滚动到底部时,如果删除一个项目,使得集合视图的内容大小必须缩小以容纳新的项目数(即删除底行的最后一个项目),则从滚动到底部的单元格行顶部被隐藏。在删除动画结束时,顶行出现时没有动画 - 这是一个非常不愉快的效果。
慢动作:
重现起来非常简单:
创建一个新的单视图项目并更改默认值ViewController
是一个子类UICollectionViewController
Add a 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:
。这是在应用单元格起始属性之前最终要求数据源提供新单元格的地方,这似乎是失败点,因为它在应该调用的时候没有被调用。
另外,这似乎与更新有关动画片,或者至少单元格没有更新duration的插入/删除。例如,以下工作没有故障:
- (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 中出现问题。