Search code examples
iosobjective-cswiftuicollectionviewuicollectionviewlayout

Behavior of UICollectionViewLayout.layoutAttributesForElementsInRect


I have implemented my own collection view layout subclassed from UICollectionViewLayout. In prepare i calculate and cache layout attributes. In layoutAttributesForElementsInRect i filter out the ones that are out of rect:

override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
    return itemAttributesCache.filter { $0.frame.intersects(rect) }
}

I expected that data source method func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell will be called for every index path that has been returned in layoutAttributesForElementsInRect

However i found if i just return the entire set of attributes like that:

override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
    return itemAttributesCache
}

cellForItemAt method is still called only for attributes inside visible rect.

Is it the expected behavior? And if yes what is the purpose to filter attributes in my code, if collection view does it itself? I did not find any performance issues with it.

Is it possible to force collectionview to call cellForItemAt for all returned attributes?


Solution

  • Yes, this is the expected behaviour of the collection view. In the docs for UICollectionViewLayout Apple says (emphasis added):

    The job of a layout object is to determine the placement of cells, supplementary views, and decoration views inside the collection view’s bounds and to report that information to the collection view when asked.

    And then continues:

    The collection view asks its layout object to provide layout information for these elements at many different times. Every cell and view that appears on screen is positioned using information from the layout object.

    The purpose of the filtering attributes in your code is improving performance. There are mainly three factors that influence the performance of your layout object:

    • The number of items in the collection view data source.
    • The complexity for determining the layout attributes.
    • The amount of memory your custom layout attributes require.

    In the cases where the above conditions aren't friendly (you have a lot of items, it's very complex to calculate the attributes and your custom attributes require a lot of memory) calculating all the attributes in advance wouldn't be feasible because it would take a lot of time and resources to pre-compute those values, so instead of caching them in the prepare function, you will have to implement some logic in the layoutAttributesForElementsInRect for computing the layout attributes. In that case you use the visible rect param for computing only the values of the attributes for the cells and views that will be visible in the screen.

    About forcing collection view to call cellForItemAt for all returned attributes, I'm not sure what would be the value of doing that since it will only use a couple of them. The only way to achieve that would be having a collection view the size of the content you are displaying, so that all cells would be consider as visible. However, doing so invalidates the point of using a collection view in the first place, since cells wouldn't be reused and it won't scroll. If you are trying to fix some weird animation with the collection view items, this isn't the way to go.