3
votes

I've implemented sticky section headers for a collection view, using http://blog.radi.ws/post/32905838158/sticky-headers-for-uicollectionview-using as a jumping off point, and they work.

But I'm seeing a very weird crash.

When I push an editor detail view, change the item's name such that it moves from one section to another ( think, changing the last name of a person in a contacts list which is grouped by first letter of last name), and then pop back to the collection view it crashes with a complaint

UICollectionView received layout attributes for a cell with an index path that does not exist

Here's the flow of execution:

In the editor detail view, I change the item's name such that it will move from section X to section Y.

The item's model emits a "name changed" notification.

The root view controller which owns the collection view catches that "name changed" notification, rebuilds its internal indexes, and then calls -reloadData on the collection view. This is all good.

I hit the Back button in the UI, and the following flow happens ( confirmed via debugger and caveman NSlog calls )

  • numberOfSectionsInCollectionView: is called, and my code returns correct number of sections
  • collectionView:numberOfItemsInSection: is called for each section, and correct number of items is returned
  • my custom flow layout's -layoutAttributesForElementsInRect: is called. The first thing I do in there is call [super layoutAttributesForElementsInRect] to get a baseline layout.

Logging the the inherited baseline layout I see attributes for the previous arrangement of cells, not the current one. E.g., the layout is for the arrangement before the edits I just made. So, incorrect sections and or cells in incorrect sections.

Now here's what blows my mind.

If I comment out the entire implementation of -layoutAttributesForElementsInRect, it still crashes. But, if I comment out:

- (BOOL) shouldInvalidateLayoutForBoundsChange:(CGRect)newBound {
    return YES;
}

Then, it works correctly.

This tells me that the collection view, or something in flow layout is caching results, but only if the flow layout shouldInvalidateLayoutForBoundsChange

Note, if I just use a vanilla UICollectionViewFlowLayout everything works fine.

TLDR

Custom UICollectionViewFlowLayout, if -shouldInvalidateLayoutForBoundsChange returns YES, gets out-of-date layout attributes from [super layoutAttributesForElementsInRect]

Any ideas?

1
Make sure that you are storing layoutAttributes for each type of element against respective indexPath in separate dictionaries in prepareLayout method. Return the same layoutAttribute for respective index from respective element dictionaries. - dev gr
Did you figure this out? I think I'm having pretty much the exact same problem. - Hilton Campbell
I did "fix" it, but in a hacky manner. I ended up having my collection view note if it was /hidden/ when changes came in, and to defer a call to reload data in this situation to viewDidAppear: - TomorrowPlusX

1 Answers

0
votes

I solved this by removing the shouldInvalidateLayoutForBoundsChange: override, and instead implementing scrollViewDidScroll: on the collection view's delegate to invalidate layout:

override func scrollViewDidScroll(scrollView: UIScrollView) {
    collectionView?.collectionViewLayout.invalidateLayout()
}

This keeps the sticky headers but stops the crashing.