Search code examples
iosswiftuicollectionviewuicollectionviewlayout

Collection view grid last row appears differently despite equal widths - swift iOS


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://stackoverflow.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).


Solution

  • 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)
    }