7

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

DrWhat
  • 1,858
  • 3
  • 14
  • 28
  • Can you please share sample demo code with us – Hitesh Surani May 08 '19 at 07:53
  • @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 May 08 '19 at 11:55
  • 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 May 08 '19 at 12:01
  • I created and uploaded a project to demo the problem: https://github.com/DrWhen/Last-row-in-grid-error – DrWhat May 13 '19 at 07:36

3 Answers3

3

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()
GaétanZ
  • 4,180
  • 1
  • 19
  • 30
  • Thanks - that makes a lot of sense, and gets very close to the solution in a very easy way. Only... in the GitHub project, if you do your fix, go to the offending screen (Articles and Adjectives) and choose the lowest value in the picker (Adjective with indefinite article ('a')), then the spacing issue is still there?!? – DrWhat May 28 '19 at 12:51
  • I changed your `sizeForItemAt` method and used ```let width = availableWidth / CGFloat(collectionContents.cellsPerRow5); return CGSize(width: width, height: 30).xx_rounded()``` I don't know what `-1` & `settings.portion` are supposed to do. – GaétanZ May 28 '19 at 14:00
  • I use settings.portions so that some columns are wider than others. For the 5 cells in the row - .portions are 1.4 for the first column and 0.9 for the other 4 columns. I need to be able to do this. "-1" was a way also to cope with left over remainders. – DrWhat May 28 '19 at 14:24
  • If you remove `-1`? – GaétanZ May 28 '19 at 16:32
  • On some devices (eg X), without the -1 the total widths is larger than the row width and it spills onto next row and destroys the grid. On Git Hub, DeutschGramma.zip shows this. Updating this file with your approach, file with today's date - the problem occurs if you navi to "Articles and Adjectives" and choose the lowest value in the picker ("Adjective with indefinite article ('a')"). Bottom row of grid. – DrWhat May 28 '19 at 19:22
  • You're missing a `)` at the end of `let multiplier = ...` – andrewcar Jan 07 '20 at 18:35
0

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

Hitesh Surani
  • 9,976
  • 5
  • 37
  • 55
  • Thanks for taking a look. While this solution does fix the issue on some devices in some orientations, it does not work in all cases. I put the entire project, with the above changes, onto the same GitHub folder for download. When running - go to "Artilcle and Adjective Endings" to see grid table. If you run this in portrait on an iPhone 8, the grid breaks completely, or on iPad Pro, the bottom row is again different to other rows. Also if you use the picker to set values in table, the picker value "Adjective with indirect article" also mucks up the last row. – DrWhat May 15 '19 at 09:38
0

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)
}
DrWhat
  • 1,858
  • 3
  • 14
  • 28