If I call -[UICollectionView performBatchUpdates:]
from inside viewWillAppear:
, inside viewDidAppear:
, between these methods, or anytime t
UICollectionView seems to have special behavior (a bug?): if it needs layout, then performBatchUpdates:
effectively acts as a reloadData
before calling the update block, which renders whatever changes you plan to make during the update block poisonous to the collection view bookkeeping.
If you plan to have batch updates applied to the view before it's been properly laid out (like from a notification handler in a rapidly changing data model environment for instance), you need to make sure that in viewWillAppear:
that you call layoutIfNeeded
on the collection view. This will prevent the collection view from reloading in the call to performBatchUpdates:
.
We discovered this behavior by putting a log in our collection view data source numberOfSections
method and printed out this backtrace to see where it was called from:
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
...
Here you can plainly see that the call to performBatchUpdates:
is hitting the datasource and becoming consistent with the changed model before the updates are applied. When the block itself is then called, the collection view will throw an assert like I showed in the original question.
tl;dr - When UICollectionView needs layout, performBatchUpdates:
effectively acts as a call to reloadData
and will make the batch update block assert because of bad bookkeeping. Call layoutIfNeeded
in viewWillAppear:
to avoid this behavior.
This is expected behaviour and not a bug. From the documentation on performBatchUpdates:
If the collection view's layout is not up to date before you call this method, a reload may occur. To avoid problems, you should update your data model inside the updates block or ensure the layout is updated before you call performBatchUpdates(_:completion:).