3

I am using the UICollectionViewCompositionalLayout to layout my cells in a collectionView. I want the width of the items in a row to auto adjust so as to fill the collectionView's width. However this is not happening even after setting the fractionalWidth to 1.0 on groupSize. Here is the code:

override func viewDidLoad() {
    super.viewDidLoad()
    collectionView = UICollectionView(frame: .zero, collectionViewLayout: createCollectionViewLayout())
    collectionView.backgroundColor = UIColor(red: 157.0/255.0, green: 159.0/255.0, blue: 162.0/255.0, alpha: 0.5)
    collectionView.register(CollectionViewCell.self, forCellWithReuseIdentifier: "Cell")
    collectionView.dataSource = dataSource
}

private func createCollectionViewLayout() -> UICollectionViewLayout {
    // Define Item Size
    let itemSize = NSCollectionLayoutSize(widthDimension: .estimated(100.0), heightDimension: .absolute(52.0))
    
    // Create Item
    let item = NSCollectionLayoutItem(layoutSize: itemSize)
    
    // Define Group Size
    let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .absolute(52.0))
    
    // Create Group
    let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [ item ])
    group.interItemSpacing = .fixed(1.0)
    
    // Create Section
    let section = NSCollectionLayoutSection(group: group)
    
    return UICollectionViewCompositionalLayout(section: section)
}

enter image description here

rlr
  • 320
  • 2
  • 11
  • Do you want the cells to be equal widths? If not, how do you want the widths sized? – DonMag Jan 26 '22 at 22:11
  • I want the width of each cell to be dynamically calculated. You see here in the screenshot, they are not expanding to fill the width of the screen. There is so much space on the right hand side – rlr Jan 27 '22 at 05:09
  • So... do you want the cell's *proportionally* sized? Do you want the first 3 to fit their text, and the 4th cell to stretch the rest of the way? Do you want each cell to fit its text, with enough "stretched" spacing between the cells so it fits the width? – DonMag Jan 27 '22 at 14:19
  • 1
    *"I want the width of each cell to be dynamically calculated."* --- OK, take a look at this image: https://i.stack.imgur.com/bt1Th.png --- Do you want the Cell Widths to be Equal, Proportional, or Fill (last cell stretched to fill)? – DonMag Jan 27 '22 at 19:25
  • 1
    "proportional" seems to be the thing I am looking for – rlr Jan 31 '22 at 05:59

1 Answers1

1

You're running into a couple problems.

A UICollectionView is designed to take the cell widths you have declared - explicitly or with or auto-layout determined sizes - and arranges them to "wrap" or scroll.

You're trying to get the collection view to set the sizes for you.

The most straightforward way to get the layout you want is for you (the designer/developer) to decide how wide you want your cells as percentages of the width.

So, you may want:

20% |    40%    | 20% | 20%

but you also want 1-point inter-item spacing.

Because collection views won't layout cells on "partial pixels," you need to allow for approximately 1-point inter-item spacing.

To get that, make sure you percentages add up to less-than 100%:

20% |    39%    | 20% | 20%

and use:

group.interItemSpacing = .flexible(1.0)

Here's a modified version of your createCollectionViewLayout() func:

private func createCollectionViewLayout() -> UICollectionViewLayout {

    // array of cell widths as Percentages
    //  total must be less than 1.0 to leave space
    //  for inter-item spacing
    let percentageWidths: [CGFloat] = [
        0.20, 0.39, 0.20, 0.20, // total == 0.99
    ]

    // array of NSCollectionLayoutItem
    var items: [NSCollectionLayoutItem] = []
    percentageWidths.forEach { w in
        let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(w), heightDimension: .absolute(52.0))
        let item = NSCollectionLayoutItem(layoutSize: itemSize)
        items.append(item)
    }

    // Define Group Size
    let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .absolute(52.0))
    
    // Create Group with items array
    let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: items)

    // use .flexible(1.0) for .interItemSpacing
    //  so we get *about* 1.0-points between cells
    group.interItemSpacing = .flexible(1.0)

    // Create Section
    let section = NSCollectionLayoutSection(group: group)
    
    return UICollectionViewCompositionalLayout(section: section)
    
}

Since you said: "proportional" seems to be the thing I am looking for ... that might be sufficient for you.

If you really want exactly 1-point-wide inter-item spacing, you'll have some more work to do... you'll need to calculate your actual cell widths and convert them to percentages of the total, allowing for an extra 3-points for the spacing, and then round the values so they don't fall on partial-pixels.

DonMag
  • 69,424
  • 5
  • 50
  • 86
  • I cant do this @DonMag.. The number of columns comes from the server and the title of each column too comes from the server. I cannot hardcode the percentage width of each column – rlr Feb 22 '22 at 18:25
  • @rlr - OK, then you need to provide more information for your question. What kinds of titles will you be getting? What happens if you get 12 titles - how do you expect that to fit? – DonMag Feb 22 '22 at 18:52
  • Hey DonMag! Great answer. Do you have any idea what to do if we want dynamic percentage calculation? (I'm trying to sort of subclass to CV to support any cell as long as it's constraint approptaite for self-sizing). Basically, can the system understands we want "fill behavior", and it'll take into account the ratios of the cells? (assuming dynamic width, absolute height) – Roi Mulia Oct 31 '22 at 12:18
  • @RoiMulia - are you sure you want "proportional, dynamic" widths? Like this: https://i.stack.imgur.com/WsGdR.png / https://i.stack.imgur.com/tqEGH.png -- and, as I asked in the previous comment, "what happens if the labels are way too wide?" You might want to post this as a new question, with details of how exactly you expect it to work? – DonMag Oct 31 '22 at 17:36
  • Hey @DonMag, thank you for the visuals. What I had in mind is similar to UIStackView fill proportionally behavior. In theory, If the labels are too long they will break, but still in proportion. I made an example for you: https://ibb.co/R9jSZXs The example is just a UIStavkView with fill proportionally. I can open a new question if you want. I'm trying to understand if there's a way to do it without going over each element and calculating the ratio (so basically make it as smooth as we can from a code perspective). Is it possible? – Roi Mulia Nov 01 '22 at 17:44
  • @RoiMulia - as far as I know, there is no built-in "fill proportionally" feature. It's not difficult to loop through the data to calculate the proportional cell widths. If you post this as a new question, I can give you an answer (way too much to "tack-on" to this one). – DonMag Nov 02 '22 at 14:40
  • Hey @DonMag, got it. I posted a question which is a follow-up to where we stopped. I basically solved it using classic FlowLayout but encountered a super weird issue with contentOffset. Would you mind taking a look? https://stackoverflow.com/questions/74295518/uicollectionview-with-dynamic-sizing-cells-resets-content-offset-on-reloaddata-o There is a demo project attached for your convenience. – Roi Mulia Nov 02 '22 at 20:56
  • @RoiMulia - ok, a little confusing... are you trying to create proportional-width cells ***to fit the collection view width***? Or, are you trying to create proportional-width cells that ***scroll horizontally***? – DonMag Nov 02 '22 at 22:21
  • Hey @DonMag, In the question I posted it's not what we spoke about, but from the same "area". I'm planning to fix this issue, then actually use ratio calculation to make the *fit to cv width*. Then basically have some kind of enum that lets me choose what layout I prefer. I'll post it later as an open-source CV subclass. Now I'm struggling with the question I opened ah, so tiering wasted hours on it already – Roi Mulia Nov 02 '22 at 22:46