swiftuicollectionviewreloaddataautosizeuicollectionviewflowlayout

UICollectionView with dynamic sizing cells resets Content Offset on reloadData on iOS 13/14


We successfully created UICollectionView with dynamic sizing cells.
The initial layout is perfect and the cells look good with no warnings. But when we use reloadData after selection occurs the UICollectionView contentOffset resets to zero.

I have created a demo project showing the issue: https://github.com/SocialKitLtd/selfSizingIssue/

In general, we set up the UICollectionFlowLayout with UICollectionViewFlowLayout.automaticSize and set up the constraints within the cell correctly (hence the proper layout).

The only "fix" that we did that works (which is not reliable at all), is to manually setting the contentOffset again after reloading the data.
We don't see a reason for the content reset to occur, feels like we are missing something.

Any help will be highly appreciated!
Video demonstrating the issue:

enter image description here


Solution

  • It looks like you're doing a whole lotta stuff that doesn't need to be (and shouldn't be) done.

    Couple examples...

    1 - You're overriding intrinsicContentSize and contentSize, and calling layout funcs at the same time. But... you have set a size for your collection view via constraints. So, intrinsicContentSize has no effect, but the funcs you're calling in contentSize may be causing issues.

    2 - You're setting flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize and returning .init(width: 1, height: 1) for sizeForItemAt ... you should use a reasonable estimated size, and not implement sizeForItemAt.

    3 - There is no need to reloadData() to update the appearance of the selected cell... Collection Views track the selected cell(s) for us. It's easier to override var isSelected: Bool in the cell class. Unless...

    4 - You are repeatedly calling reloadData(). When you reloadData(), you are telling the collection view to de-select any selected cells.

    I put up a modification to your repo here: https://github.com/DonMag/CollectionViewSelfSizingIssue


    Edit - After comments...

    OK - the intrinsicContentSize implementation is there to allow the collection view to be centered when we have only a few items. That appears to work fine.

    The reason the collection is getting "shifted back to the start" on reloadData() is due to this:

    public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        return .init(width: 1, height: 1)
    }
    

    I'm not an Apple engineer, but as I understand it (and this can be confirmed with some debug print() statements):

    if we do this:

    items = (0..<150).map { "\($0)" }
    

    sizeForItemAt will be called 150 times before the first call to cellForItemAt.

    So, if we have 10 items, and we're returning a width of 1 for every cell, the collection view thinks:

    10 * 1 == 10
     9 * 5 == 45  // 9 5-point inter-item spaces
    ------------
    Total collection view width will be 55-points
    

    And, naturally, it shifts back to starting with cell 0, because all 10 cells are going to fit.

    I've updated my repo to allow both collection views to "center themselves" when there are only a few items.