'how should the math of UICollectionViewFlowLayout spacing work?

I'd like someone could explain clearly how I should calculate the math for the UICollectionViewFlowLayout, exactly "the subtract spacing" thing from itemSize width/height. in this example I get a squared view, 3 items in 3 columns.

  1. why if I use

width: (view.frame.width/3)-testSpacing-1.5

the grid gives standard two columns and empty at the center? and if is use:

width: (view.frame.width/3)-testSpacing-6

nothing seems to change?

  1. Am I doing it right? but why someone told me I should subtract half of the spacing and not the whole value?

  2. Is it normal that if I rotate device the space between cells becomes greater? is there a "pattern" to manage that thing?

var collectionData = ["1🐶", "2🐱", "3 🐼", "4🐯", "5🐙", "6🦕", "7🦖", "8🦀", "9🐬", "10🦋", "11🐠", "12🦇","1🐶", "2🐱", "3 🐼", "4🐯", "5🐙", "6🦕", "7🦖", "8🦀", "9🐬", "10🦋", "11🐠", "12🦇"]

let testSpacing: CGFloat = 2

let layout = UICollectionViewFlowLayout()
layout.itemSize = CGSize(
    width: (view.frame.width/3)-testSpacing-5,
    height: (view.frame.width/3)-testSpacing-5)

layout.minimumInteritemSpacing = 5
layout.minimumLineSpacing = 5


layout.sectionInset = UIEdgeInsets(top: 0, left: 5, bottom: 0, right: 5)
myCollectionView.collectionViewLayout = layout

my cell:

class Test_CollectionViewCell: UICollectionViewCell {
    
    @IBOutlet weak var messageLabel: UILabel!
    
    static let identifier = "Test_CollectionViewCell"
    
    private var message: String = "" {
        didSet {
            self.messageLabel.text = message
        }
    }
    
    
    func setup(message: String) {
        
        self.backgroundColor = .systemTeal
        self.messageLabel.backgroundColor = .white
        
        if message == "1🐶" {
            self.messageLabel.backgroundColor = .red
        }
        self.message = message
//        contentView.clipsToBounds = true 
    }
    
    override func prepareForReuse() {
        self.backgroundColor = .white
        self.messageLabel.backgroundColor = .white
    }
}


Solution 1:[1]

In short, when you write this code (view.frame.width/3)-testSpacing-1.5 and then this (view.frame.width/3)-testSpacing-6 in both cases, you do not have enough room in the same row to add 3 cells and hence why you see no significant differences

Let's take an example with some math for the horizontal calculation you use

  1. Let's say the value of view.frame.width was 375.
  2. view.frame.width/3 = 125
  3. 125 - testSpacing(2) - 1.5 = 121.5 which is your cell width
  4. Now you had 2 edge insets set of 5 each which makes a total of 10
  5. Plus you have a minimumInteritemSpacing between each cell set to 5 which makes a total of 10 since there are 2 gaps between the cells in a 3 column layout

UICollectionView UICollectionViewFlowLayout InterItemSpacing

  1. So let's add all the spacing up now: Since you expect 3 cells cell width(121.5 * 3) + cell spacing(10) + insets(10) = 384.5
  2. Compare the value in point 6 with point 1, clearly 384.5 > 375 which means the width taken by 3 cells, spacing and inset is more than the width of the collection view and hence collection view cannot fit your 3 columns and goes with 2
  3. Since it goes with 2, the calculation becomes cell width(121.5 * 2) + cell spacing(10) + insets(10) = 263
  4. Since the collection view's width is 375, there is excess space of 375 - 263 = 112 which is the hole you see in the middle
  5. You set the minimumInteritemSpacing to 5 which is the minimum, the max can be anything so the gap between the cells or hole you see is the 112

I hope this explains the results you see, now let's try to create a layout with the columns we want (assuming a vertical scrolling collection view)

A good idea when setting up a collection view is to figure out the available width we have to work with. This is the width of the collection view - (inter cell spacing + edge insets)

We then take this available width and divide it by the number of columns we want to see.

Let's start with code for a 3 columns layout:

private func createLayout() -> UICollectionViewFlowLayout
{
    let layout = UICollectionViewFlowLayout()
    
    layout.scrollDirection = .vertical
    
    // Padding on the left and right between the cells
    // and the collection view boundary
    let horizontalInsets: CGFloat = 5
    
    // How many columns we want
    let numberOfColumns = 3
    
    // Spacing we want between our cells
    let cellSpacing: CGFloat = 10
    
    // Since the collection view is width of the whole screen,
    // the available width for your cells is the width of the screen
    // minus the insets which are the padding on the left and right
    var availableWidth = UIScreen.main.bounds.width - (horizontalInsets * 2)
    
    // A 3 column layout will have 2 spaces between the cells
    // Between cell 1 - 2 and between cell 2 - 3
    
    // A 4 column layout will have 3 spaces between the cells
    // Cell 1 - 2, Cell 2 - 3 and Cell 3 - 4
    
    // So the number of spaces will be numberOfColumns - 1
    
    // We need to adjust the available width based on the spaces we want
    // between our cells
    availableWidth -= (cellSpacing * CGFloat(numberOfColumns - 1))
    
    // Calculate the cell width based on the space we have available
    cellWidth = availableWidth / CGFloat(numberOfColumns)
    
    layout.itemSize = CGSize(
        width: cellWidth,
        height: cellWidth)
    
    // Set the minimum between the cell spacing setting and 5
    // as the minimum spacing between items
    layout.minimumInteritemSpacing = min(cellSpacing, 5)
    
    layout.minimumLineSpacing = 5
    
    
    layout.sectionInset = UIEdgeInsets(top: 0,
                                       left: horizontalInsets,
                                       bottom: 0,
                                       right: horizontalInsets)
    
    return layout
}

I have left some comments in the code explaining some calculations, this is the output:

UICollectionView UICollectionViewFlowLayout spacing grid math calculation

Notice the values of the different items

Now since my starting point is to determine the available width, changing the number of columns I want is simple, just change this line to 4

// How many columns we want
let numberOfColumns = 4

The output you get is:

UICollectionViewFlowLayout layout grid calculation columns

Let's say you want to decrease the spacing between cells to 0 and keep only the insets, just change the cellSpacing to 0

// Spacing we want between our cells
let cellSpacing: CGFloat = 0

The result:

Cell spacing UICollectionViewFlowLayout columns grid calculation

The key is finding out the total available space using the formula above and then calculate the cell width.

Ofcourse, the same can be applied for vertical spacing with some adjustments.

I hope this helped and gives you some insight into the calculations and results you were seeing with some ideas on getting your desired layout.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1