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.