I'm trying to understand how UICollectionViewLayout
works and found this sample code.
https://developer.apple.com/documentation/uikit/uicollectionview/customizing_collection_view_layouts
In the sample code(and all other examples I could find), it has cachedLayoutAttributes
object and calculates all items' layout in prepare()
.
I found 2 issues for this pattern.
One is if the number of data source is big enough, calculating everything in the prepare()
method will take time(and it will be recalculated whenever collectionView's layout changes), so it doesn't look very efficient.
The other issue is that there is no chance layoutAttributesForItem
or layoutAttributesForSupplementaryView
get called. Only layoutAttributesForElements
gets called and it seems it is sufficient enough to layout cells/supplementary views.
I read documentations but it only says the methods must be override. My best guess is that I should use override those methods to return an appropriate UICollectionViewLayoutAttributes
and call them from layoutAttributesForElements
. However this layoutAttributesForElements
gets called whenever user scrolls UICollectionView, so it also seems not very efficient.
I want to know what is the case layoutAttributesForItem
and layoutAttributesForSupplementaryView
get called, and what/when/where is the best way/place/time to calculate UICollectionViewLayoutAttributes
if the number of data source is huge.
Update
Thanks for the comment. However it seems like it's about optimization in invalidation cycle.
Layout objects that are designed to support invalidation contexts can use the information in a UICollectionViewLayoutInvalidationContext object to optimize their behavior during the invalidation cycle.
Which means the UICollectionViewLayoutAttributes
are already set and invalidate only necessary part, right?
So, where should I calculate UICollectionViewLayoutAttributes
for the first time? Please let me know if I'm wrong.
One is if the number of data source is big enough, calculating everything in the
prepare()
method will take time(and it will be recalculated whenever collectionView's layout changes), so it doesn't look very efficient.
Yeah, it doesn't look very efficient. It's your job to make prepare()
calculations as efficient as possible.
For example: calculate only a portion of attributes and calculate more when scroll changed its position using shouldInvalidateLayout(forBoundsChange:)
. But be careful with this method because it might invalidate the layout on every pixel scroll.
prepare()
method is not called just for fun, it informs you that something is changed and recalculation is required! But the information about changes is hard to get. You can check how IGListKit gets information about changes before prepare: link.
The other issue is that there is no chance
layoutAttributesForItem
orlayoutAttributesForSupplementaryView
get called. OnlylayoutAttributesForElements
gets called and it seems it is sufficient enough to layout cells/supplementary views.
layoutAttributesForElements(in:)
is called first because the collection doesn't know about the order of your cells and supplementary views. So it asks for all attributes in the specified rectangle. Try to insert/move/delete/reload cells, I believe layoutAttributesForItem(at:)
might be called. Or maybe not. But you should not worry about it because you already have cache of attributes.
Since you cache attributes layoutAttributesForElements(in:)
should be efficient enough and will look something like this:
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
return attributes
.reduce(into: [UICollectionViewLayoutAttributes](), { $0 += $1.filter({ $0.frame.intersects(rect) }) })
}
I personally suggest to look inside some difficult open-source layout. You will get much more information about the UICollectionViewLayout
compared to information from any simple sample project.