When inserting multiple custom supplementary views in a collection view with existing supplementary views, using a subclass of UICollectionViewFlowLayout, the collection view appears to create glitched animations or otherwise not handle the insertions correctly.
Inserting one supplementary view behaves as expected, but inserting two supplementary views at once results in an obvious visual glitch, and inserting many supplementary views at once (e.g. 4+) worsens the glitch. (iOS 10.2, iOS 9.3, Swift 3, Xcode 8.2.1)
Here's a demonstration:
You can reproduce the problem using this sample project. In this simplified example, there is one supplementary view for each item, and two items (with two supplementary views) are inserted during each batch update. In my real project I have fewer supplementary views than items and I utilise other flow layout features.
My best guess is that something is causing the collection view to confuse the index paths or layout attributes corresponding to existing/inserted supplementary views. If this turns out to be a bug in one of Apple's classes, I would be very grateful for your most elegant workaround.
Attempted solutions
I've tried and double-checked each of the following:
Implementing
indexPathsToInsertForSupplementaryView(ofKind:)
—so far as I can tell, this is straightforward and my implementation returns the correct index paths. The views are always inserted. For supplementary header and footer views, UICollectionViewFlowLayout seems to return a sorted array of index paths, but sorting the array hasn't made any difference for me.Providing initial and final layout attributes—calling
super
for these methods appears to return the correct frames, so it is not obvious what adjustments (if any) could be made to the initial or final layout attributes to solve the problem. But see the third curiosity noted below.Invalidating the layout—it seems to make no difference at all whether the layout is manually invalidated in whole or in part before, during, or after
performBatchUpdates()
, or at all.Custom index paths—although index paths for supplementary elements do not need to correspond to item index paths, so far as I can tell, and despite documentation to the contrary, UICollectionViewFlowLayout will crash (by requesting layout attributes for non-existent index paths) unless supplementary elements of the same kind are numbered sequentially from 0. I assume this is how the collection view and/or the layout object is able to compute new index paths when elements are inserted or deleted (i.e. by incrementing or decrementing the
item
property of the index path). So constructing arbitrary index paths is not an option.
Curiosities noticed while debugging
The visual glitches are specific to supplementary views and do not occur for inserted items, even when the index paths and frames are the same for both.
Debugging the view hierarchy reveals that, although the frames of the inserted supplementary views are correct, the frames of some existing supplementary views are not correct. That is so despite the fact that the frames returned by the layout object for the existing views at those index paths appear to be correct.
When inserting multiple supplementary views, the calls made by the collection view for initial and final layout attributes appear to be unbalanced. That is, initial layout attributes are requested multiple times for the same index path (and, naturally, the same attributes are returned each time), and there are fewer requests for final layout attributes. Further, for some existing supplementary views, initial and final layout attributes are never requested at all. I find this suspicious. It leads me to believe that the collection view is somehow confusing supplementary views and/or index paths before and after the update.
The memory addresses for instances of IndexPath created by my Swift code are strikingly different to the instances of NSIndexPath created internally by UICollectionView and UICollectionViewFlowLayout. The former are always different (as expected), while the latter appear to be identical between launches (e.g. [0, 0], [0, 1] and [0, 2] are always at
0xc000000000000016
,0xc000000000200016
and0xc000000000400016
). In Swift, IndexPath is bridged to NSIndexPath, but the former is a value type whereas the latter is a reference type. NSIndexPath also uses tagged pointers. There is a remote possibility that the two have been imperfectly bridged and/or that the collection view classes rely internally on NSIndexPath behaviour in some way that gives rise to the index path confusion apparent here. I don't know how to test this or take this further.
Similar questions
The following questions may be related, although none of the answers worked for me:
How can UICollectionView supplementary views be inserted or deleted correctly
layoutAttributesForSupplementaryViewOfKind:atIndexPath: passes in incorrect indexPath
UICollectionView supplementary views won't animate “in” or “out”
UICollectionView Decoration and Supplementary views can not be moved
The problem may also be related to this bug report on Open Radar. The sample project accompanying that report is a bit too complicated to be of much use though.
Apple Technical Support
After posting this question, I submitted a Technical Support Incident to Apple. Apple Developer Support replied as follows:
Our engineers have reviewed your request and have determined that this would be best handled as a bug report.
Please submit a complete bug report regarding this issue using the Bug Reporting tool at https://developer.apple.com/bug-reporting/.
...
We’ve spent some time investigating the possibility of a workaround and unfortunately have come up empty handed. Please follow up with your bug. If we find the possibility of some kind of workaround in the future, we will reach out. Sorry I don’t have better news.
If you experience this problem, please duplicate rdar://30510010.