UICollectionView performBatchUpdates:如果视图需要布局,则意外断言?

Posted

技术标签:

【中文标题】UICollectionView performBatchUpdates:如果视图需要布局,则意外断言?【英文标题】:UICollectionView performBatchUpdates: asserts unexpectedly if view needs layout? 【发布时间】:2014-11-12 23:46:24 【问题描述】:

如果我从viewWillAppear: 内部、viewDidAppear: 内部、在这些方法之间调用-[UICollectionView performBatchUpdates:],或者任何时候集合视图没有被更大的 UIView 视图层次结构布局,集合视图将断言:

*** 由于未捕获的异常“NSInternalInconsistencyException”而终止应用程序, 原因:'无效更新:无效的部分数。节数 更新后集合视图中包含的 (X) 必须等于数字 更新前集合视图中包含的部分 (X),加号或减号 插入或删除的节数(X 插入,0 删除)。'

其中“X”是我插入的任何数量的项目。我已经确认数据源正在正确更新,并且更新中的索引路径是正确的。

如果在调用performBatchUpdates: 之前刚刚更新了数据源,那么集合视图状态如何与数据源保持一致?似乎有什么意外触发了 reloadData。

【问题讨论】:

【参考方案1】:

UICollectionView 似乎有特殊的行为(一个错误?):如果它需要布局,那么performBatchUpdates: 在调用更新块之前有效地充当reloadData,这会使您计划在更新块期间进行的任何更改都有毒到集合视图簿记。

如果您计划在视图正确布局之前将批量更新应用到视图(例如从快速变化的数据模型环境中的通知处理程序),您需要确保在您调用的 viewWillAppear: layoutIfNeeded 在集合视图上。这将防止集合视图在调用 performBatchUpdates: 时重新加载。

我们通过在我们的集合视图数据源numberOfSections 方法中添加一个日志来发现这种行为,并打印出这个回溯以查看它是从哪里调用的:

2014-11-12 15:30:06.173 CVCrasher[66830:6387719] [CV] #sections stack: (
0   CVCrasher     0x000000010ba9122d -[MyViewController numberOfSectionsInCollectionView:] + 61
1   UIKit         0x000000010cfc2811 -[UICollectionViewData _updateItemCounts] + 147
2   UIKit         0x000000010cfc4a89 -[UICollectionViewData numberOfSections] + 22
3   UIKit         0x000000010cfaebae -[UICollectionViewFlowLayout _getSizingInfos] + 348
4   UIKit         0x000000010cfafca9 -[UICollectionViewFlowLayout _fetchItemsInfoForRect:] + 526
5   UIKit         0x000000010cfab51f -[UICollectionViewFlowLayout prepareLayout] + 257
6   UIKit         0x000000010cfc2a10 -[UICollectionViewData _prepareToLoadData] + 67
7   UIKit         0x000000010cfc30e9 -[UICollectionViewData validateLayoutInRect:] + 54
8   UIKit         0x000000010cf8b7b8 -[UICollectionView layoutSubviews] + 170
9   UIKit         0x000000010c9d1973 -[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 521
10  QuartzCore    0x0000000110cf2de8 -[CALayer layoutSublayers] + 150
11  QuartzCore    0x0000000110ce7a0e _ZN2CA5Layer16layout_if_neededEPNS_11TransactionE + 380
12  UIKit         0x000000010c9c5847 -[UIView(Hierarchy) layoutBelowIfNeeded] + 611
13  UIKit         0x000000010cf9c7b7 -[UICollectionView performBatchUpdates:completion:] + 164
...

在这里您可以清楚地看到对performBatchUpdates: 的调用正在访问数据源并与更改的模型保持一致应用更新之前。然后调用块本身时,集合视图将抛出一个断言,就像我在原始问题中显示的那样。

tl;dr - 当 UICollectionView 需要布局时,performBatchUpdates: 有效地充当对reloadData 的调用,并且由于记账错误而使批量更新块断言。在viewWillAppear: 中调用layoutIfNeeded 以避免这种行为。

【讨论】:

您的意思是在viewWillAppear: 中调用layoutIfNeeded 并稍后在viewDidAppear: 中调用performBatchUpdates: 吗?所以当在viewDidAppear:中调用performBatchUpdates:时,UICollectionView会检查发现不需要再次布局,不会调用reloadData 这个该死的错误只花了一天多的时间就解决了。感谢您的回答,为我指明了正确的方向。在viewWillAppear 中执行layoutIfNeeded 对我们不起作用,因为更新开始得更早 - 但在我们的数据模型更新解决它之前执行layoutIfNeeded!这绝对应该作为一个错误提交给苹果。如果他们决定使用reloadData 而不是批量更新,则必须丢弃批量更新块以避免不一致。【参考方案2】:

这是预期的行为,而不是错误。来自performBatchUpdates的文档:

如果在调用此方法之前集合视图的布局不是最新的,则可能会发生重新加载。为避免出现问题,您应该在更新块内更新数据模型或确保在调用 performBatchUpdates(_:completion:) 之前更新布局。

【讨论】:

以上是关于UICollectionView performBatchUpdates:如果视图需要布局,则意外断言?的主要内容,如果未能解决你的问题,请参考以下文章

-[UICollectionView _endItemAnimations] 中的 UICollectionView 断言失败

如何滚动另一个 UICollectionView 下方的 UICollectionView?

UICollectionView 单元内部已加载,但 UICollectionView 未显示

UICollectionView 断言失败 -[UICollectionView _updateWithItems:tentativelyForReordering:]

UITableviewCell 中的 UICollectionView。添加约束后,UICollectionView 不显示

将标题添加到 UICollectionView,而不是 UICollectionView 的部分