My goal is to have dynamically sized cells in a UICollectionView. In addition, when the scrollable contentWidth of the UICollectionView is less than the bounds of the Collection, I want the items centred within the collection.
Approach thus far:
I am trying to do this in the cleanest way possible. I have self-sizing UICollectionViewCells where Auto-Layout can determine the size of the cell from the contraints of the content. For this, my cells override the preferredLayoutAttributesFitting function, returning the appropriate width for their content.
My custom UICollectionViewFlowLayout subclass uses this width information, provided by the UICollectionViewCell to properly size up the cells.
The only methods overridden on my custom UICollectionViewFlowLayout are:
layoutAttributesForItem(at...)layoutAttributesForElementsIn(rect...)shouldInvalidateLayout(forBoundsChange...)
The dynamic sizing of the UICollectionViewCells works flawlessly.
The problem:
Using this approach, I have no prior knowledge of the size of each cell (each can be different). I'd like to implement the UICollectionView's delegate method to provide an inset to center my content within the collection view. For this, I'm thinking I should implement and override
collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets
The problem lies in the fact that the inset method is called early in the layout process, where I have no knowledge of the contentSize that will be determined during layout.
Here's a stack of the various layout methods, for a single complete layout pass, in the order that they run:
prepare started
prepare finished
layoutAttributesForElementsInRect started
layoutAttributesForElementsInRect finished
layoutAttributesForElementsInRect started
layoutAttributesForElementsInRect finished
layoutAttributesForElementsInRect started
layoutAttributesForElementsInRect finished
prepare started
insetForSectionAt 0 called <--- The datasource has knowledge of the presence of cells but the cells themselves are not yet dequeued
prepare finished
layoutAttributesForElementsInRect started
layoutAttributesForItemAt [0, 0] started
layoutAttributesForItemAt [0, 0] finished
layoutAttributesForItemAt [0, 1] started
layoutAttributesForItemAt [0, 1] finished
layoutAttributesForItemAt [0, 2] started
layoutAttributesForItemAt [0, 2] finished
layoutAttributesForElementsInRect finished
layoutAttributesForElementsInRect started
layoutAttributesForItemAt [0, 0] started
layoutAttributesForItemAt [0, 0] finished
layoutAttributesForItemAt [0, 1] started
layoutAttributesForItemAt [0, 1] finished
layoutAttributesForItemAt [0, 2] started
layoutAttributesForItemAt [0, 2] finished
layoutAttributesForElementsInRect finished
prepare started
prepare finished
layoutAttributesForElementsInRect started
<--- At this point Cells DO return valid sizes through Autolayout
layoutAttributesForItemAt [0, 0] started
layoutAttributesForItemAt [0, 0] finished
layoutAttributesForItemAt [0, 1] started
layoutAttributesForItemAt [0, 1] finished
layoutAttributesForItemAt [0, 2] started
layoutAttributesForItemAt [0, 2] finished
layoutAttributesForElementsInRect finished
For a single layout pass, multiple calls are made to the various layout methods but only at the very end is the size information from Auto-layout available from the different cells to pass on to the layout.
I noticed that the sectionInset delegate function is getting called early, before the cells have been properly dequeued and allowed to run their Autolayout constraints. Therefore, it is impossible to inform the inset with a correct value at the time it is called.
Using such an approach, is it possible to force a call to recalculate the sectionInsetAt after the layout pass or should I change the technique completely?
Things I have tried:
I tried querying the collectionViewLayout's collectionViewContentSize from within the insetForSectionAt delegate function. Unfortunately, this cannot be used during a layout pass as the layout is currently in progress. It causes a crash with error:
[CollectionView] An attempt to update layout information was detected while already in the process of computing the layout (i.e. reentrant call). This will result in unexpected behaviour or a crash. This may happen if a layout pass is triggered while calling out to a delegate. UICollectionViewFlowLayout instance is (<App.ViewportToolbarFlowLayout: 0x7f8a5261add0>)
I tried using the collectionView's contentSize method to get at the size during the execution of the insetForSectionAt delegate method, but it does not provide correct data at the moment it runs within the layout process.
