7
votes

I've a fixed grid of collection view cells (UICollectionView) but the cells in the bottom row always appears with a slightly smaller width on screen. The frame size (or bounds) and calculated width used within collectionViewLayout: UICollectionViewLayout sizeForItemAt are the same for all rows.

enter image description here

        func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize
{
    let settings = currentContents[indexPath.item]
    let height = CGFloat(30)

    // https://stackguides.com/questions/54915227/uicollectionview-remove-space-between-cells-with-7-items-per-row
    var cellWidth = CGFloat()
    let availableWidth = collectionView.bounds.size.width
    print("available width", availableWidth)
    let minimumWidth = floor(availableWidth / collectionContents.cellsPerRow5)
    print("minmum width", minimumWidth)
    cellWidth = minimumWidth * settings.portion - 1
    print("cell width", cellWidth)

    return CGSize(width: cellWidth, height: height)
}

enter image description here

I'd like to get the bottom row to line up with the other rows, but can't imagine what is happening that is changing the widths after returning the value in the layout delegate method (or how to fix).

3
Can you please share sample demo code with usHitesh Surani
@iMHiteshSurani - which demo code do you mean? I have the full collectionViewLayout method where the cell width is set. If I delete everything but the borders in cellforitem at index path, the issue is still there - although it does seem a little less pronounced.DrWhat
If I remove the settings.portion value - so that all cells in the row are equal width - then the problem disappears. Perhaps there is a better way to assign the proportion of the total row for each column of cells? Currently, the sum of all setting.portion values simply equals the number of columns. 1st column- portion: CGFloat(1.4), and all other portion: CGFloat(0.9)DrWhat
I created and uploaded a project to demo the problem: github.com/DrWhen/Last-row-in-grid-errorDrWhat

3 Answers

3
votes

UIKit does not like values with more than 2 decimals, round them or it will do it for you.

Here, UICollectionViewFlowLayout rounds your cell size and starts to fill the lines with an "interitemspacing" at least equal to the minimumInteritemSpacing you specified. On the last line, it used exactly the minimumInteritemSpacing value and doesn't fill entirely the line.

Fix it using better rounded values, giving the illusion that all is perfectly aligned.

I usually use those extensions:

extension CGFloat {
    func xx_rounded(_ rule: FloatingPointRoundingRule = .down, toDecimals decimals: Int = 2) -> CGFloat {
        let multiplier = CGFloat(pow(10.0, CGFloat(decimals)))
        return (self * multiplier).rounded(.down) / multiplier
    }
}

extension CGSize {
    func xx_rounded(rule: FloatingPointRoundingRule = .down, toDecimals: Int = 2) -> CGSize {
        return CGSize(
            width: width.xx_rounded(rule, toDecimals: toDecimals),
            height: height.xx_rounded(rule, toDecimals: toDecimals)
        )
    }
}

Change:

return CGSize(width: cellWidth, height: height)

to

return CGSize(width: cellWidth, height: height).xx_rounded()
0
votes

Just replace

cellWidth = (minimumWidth * settings.portion) - 0.5

WITH

cellWidth = (minimumWidth * settings.portion)

Specified minimumInteritemSpacingForSectionAt to 0.5 instead of 0

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
    return 0.5
}

CVTVCell.swift

Just reload your UICollectionView in awakeFromNib method after your data set into currentContents. Please refer below sample code.

override func awakeFromNib(){
    super.awakeFromNib()
    currentContents = collectionContents.allEndings[0]
    self.cView.reloadData()
}

Download code from here

0
votes

The best way I found was to capture the remaining space (minus 1 to avoid rounding up) after calculating the initial column width, and then use this remaining space for additional columns.

For a two column example:

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize
{
    if indexPath.item == 0 || indexPath.item == 1
    { rowHeight = 40 }
    else { rowHeight = 30 }
    var cellWidth = CGFloat()
    let minimumWidth = floor(availableWidth / numberOfColumns)
    var columnPortion = CGFloat()
    let columnNumber = indexPath.item % Int(numberOfColumns)
    switch columnNumber
    {
    case 0:
        columnPortion = columnPortion1
        cellWidth = minimumWidth * columnPortion - 10
        remainder = availableWidth - cellWidth - 1
    case 1:
        columnPortion = columnPortionFinal
        cellWidth = remainder
    default:
        break
    }
    return CGSize(width: cellWidth, height: rowHeight)
}