1

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

Roi Mulia
  • 5,626
  • 11
  • 54
  • 105
  • Looks like a common bug on those iOS versions. Please check this: https://stackoverflow.com/questions/46827379/uicollectionview-automatic-size-reloaddata-resets-contentoffset-to-0-top – Konstantin Nov 02 '22 at 21:39
  • Is your sole purpose of calling `reloadData()` to change the label background color(s)? – DonMag Nov 02 '22 at 23:03
  • @DonMag Not only, but it can also be a valid change to the cells. I thought about going over the visible cells and changing it in the view level, but it's supposed to be a generic class so I'm trying to find a solid/appropriate solution. Did you try the demo? – Roi Mulia Nov 02 '22 at 23:07
  • @DonMag I just worked so hard to make it work, and it fails in a basic task as reloadData. Other then this it's perfect – Roi Mulia Nov 02 '22 at 23:07
  • @RoiMulia - I’ll have a response for you tomorrow. – DonMag Nov 02 '22 at 23:30
  • Okay @DonMag, I appreciate your help and attitude. Really! Thank you and good night – Roi Mulia Nov 02 '22 at 23:33

1 Answers1

1

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):

  • on reloadData(), the collection view calls sizeForItemAt for every item in the data set
  • the collection view then starts laying out the cells

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.

DonMag
  • 69,424
  • 5
  • 50
  • 86
  • Hey DonMag! Thank you for the answer. I have 2 questions and 2 note: Notes: As for why I implemented sizeForItem, for some weird reason I got a warning on undefined behavior, and implementing it solved the issue (and I agree it was weird, but it's the only thing that worked). As for why I implemented intrinsic content size it's because when the collectionView items are less than the specified within the storyboard (i.e equalAndLess than 300), it will make the content shrink then the cells will be centered. – Roi Mulia Nov 03 '22 at 14:29
  • If you will open your storyboard and change the StackView alignment to .center (the top StackView) and run the app you'll see that without the intrinsicContentSize override you won't see your CV. sample: https://ibb.co/7ktNfR3 . I also changed the array to be less items so it will need to shrink (that's why I use width <= 300 on container to allow it to shrink) – Roi Mulia Nov 03 '22 at 14:30
  • For my two questions: - If i use flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize won't it make the system make it fully dynamic? Or settings an okay estimated size is good enough as well? - I have reloadData() on your CV on a selection that contentOffset returns to .zero as well. Like my original question example, can it be solved? I'm aware of the use of selected but I'm trying to fix it from a reloadData perspective as selected is not always the best approach IMO. – Roi Mulia Nov 03 '22 at 14:35
  • @RoiMulia - see the **Edit** to my answer -- my GitHub repo is also updated. – DonMag Nov 03 '22 at 15:15
  • Hey @DonMag, got it. Thanks. So basically we are stuck with this bug on iOS 13/14 (as for iOS 15/16 it seems to reload correctly). Is it critical that we don't specify .automaticSize at this point? I'm not a fan of isSelected but seems like there is no choice on older iOS. – Roi Mulia Nov 03 '22 at 15:21
  • At this point, I'm wondering if I just make the generic class with reloadData() and absorb the iOS 13/14 issue. I'm wondering about the .automicSize if it's better just to set it like this instead of minimumSize instead – Roi Mulia Nov 03 '22 at 15:28
  • @RoiMulia - I wonder if you explain exactly what you're trying to accomplish, if there might be a better way to do it rather than using a collection view. However, if you want to stick with your current approach, you can return the actual cell size in `sizeForItemAt` and avoid the "shifting" -- see my updated repo... added a modification to your class, with a "code created" cell (instead of a xib) – DonMag Nov 03 '22 at 16:27
  • Hey @Don! Thank you for the last update, seems to work much better now. The reason why I'm "insisting" on making it work with reloadData() and making it work in a few different UI scenarios is that we use those components in a wide range of projects. Some of the project architectures make the data handling a bit more complex for us, so reloadData() is a good way to "clean the table" for quick implementation. I think using SizeForItem with size calculation is a good way. I also added +20 points to the calculated width and seems to work just fine (on purpose, wanted to test it) – Roi Mulia Nov 03 '22 at 17:09
  • My question: Because with set .automaticSize, we don't need it to be exactly 1:1 in cell size? Can it be just close enough for it not to jump? I'm asking it to understand how important is accuracy (1:1 size calculation) to guarantee it works properly with no jumping – Roi Mulia Nov 03 '22 at 17:10
  • @RoiMulia - *"added +20 points to the calculated width"* ... that kills the "centering with only a few cells" design. – DonMag Nov 03 '22 at 17:15
  • Are you sure? I just tried it on iOS 13 - 15 and it seems to work: https://ibb.co/MRrCDwJ – Roi Mulia Nov 03 '22 at 17:29
  • @RoiMulia - this is what I get when I return `return .init(width: s.width + 20.0, height: s.height)` -- https://i.stack.imgur.com/FkDz4.png – DonMag Nov 03 '22 at 18:28
  • That's weird haha. I created a new repo: https://github.com/SocialKitLtd/V2CollectionViewSelfSizingIssue-main I tried both with a long and short array, please check AdaptiveTitleCollectionView (original class). Are you still seeing it not centered? – Roi Mulia Nov 03 '22 at 22:19
  • My bad, I was looking at the wrong collection view. sorry! – Roi Mulia Nov 03 '22 at 23:06