43
votes

I have a added a UICollectionView to my UIViewController and have made a custom cell for it.

I set the size of the cell using the sizeForItemAtIndexPath method and set each cell to be UIScreen.mainScreen().bounds.width/2 in both height and width. So essentially, each cell is half the screen wide and this same length in height.

I then have an UIImageView inside the cell with each edge pinned to its relative edge of the cell so that the image view fills the cell. I also have a UILabel that is centred both vertically and horizontally in the cell.

However on first load, the cell is the correct size but the content is much smaller in a tiny square in the top left. (Seen in the image below, I have set the cell's contentView background color to green and the imageView background color to red).

enter image description here

Then after scrolling down a little bit (and I assume a reusable cell being dequeued) all of the cells are fine (and remain fine after scrolling back up).

enter image description here

What is causing the content to not be constrained properly on first load?

UPDATE

Some of the code from my UIViewController:

func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
        var cell = collectionView.dequeueReusableCellWithReuseIdentifier("PrevDrawCell", forIndexPath: indexPath) as? PrevDrawCell

        if cell == nil {
            cell = PrevDrawCell()
        }

        cell!.drawVC = self
        cell!.imageShortCode = self.tempImageURLs[indexPath.row]

        // Pull image (async) and set after download
        cell!.setupImage()

        cell!.contentView.backgroundColor = UIColor.greenColor()
        cell!.coverView.backgroundColor = UIColor.redColor()

        cell!.dateLabel.font = UIFont(name: "HelveticaNeue-Light", size: 26)
        cell!.dateLabel.text = "\(self.tempImageURLs.count-indexPath.row)/9"

        return cell!
}

func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
    let size = UIScreen.mainScreen().bounds.width/2
        return CGSize(width: size, height: size)
}


func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAtIndex section: Int) -> UIEdgeInsets {
        return UIEdgeInsetsZero
}

func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAtIndex section: Int) -> CGFloat {
    return 0
}

func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAtIndex section: Int) -> CGFloat {
    return 0
}
8
Show UIViewController - Arsen
You mean show the code for the UIViewController? - myles
I've added some of the code for the UICollectionView. Not sure what exactly you want to see? - myles
Almost looks like the default 100x100px cell size. If you use the view debugger in Xcode, is it the cell itself or the contentView that still uses the default size? What happens if you call layoutIfNeeded after you set everything on your cell? - Lucas van Dongen
@DepartamentoB In the view hierarchy debugger when I select the actual cell itself it is the size of the green background, not the little red square. Also, just tried calling cell.layoutIfNeeded() just before I return cell in the cellForRowAtIndexPath method and this solves it! Is this a good thing to be doing here? What exactly is it doing and are there any downsides? Should it be called in cellForRowAtIndexPath where I have put it or somewhere else instead? - myles

8 Answers

75
votes

It looks like it still retains it's 100x100px basic size for the contentView while the cell itself gets the right size already. You can force the layout to reset adding the following line just before you return the cell:

cell?.layoutIfNeeded()
38
votes

What worked for me was changing Estimate Size on the Collection View from Automatic to None.

collection view estimate size

6
votes

I had once similar problem (content of my cell was not fitting cell even with correct autolayout). The bug was caused by not calling super.layoutSubviews() in overriden function layoutSubviews() that was in Cell's class.

3
votes

For those using Storyboard + AutoLayout, and choosing which constraints are active at runtime:

When using dequeueReusableCell, on first load, the cell is created but its view has not been initialised, so the constraint changes aren't saved - whatever you have set in Storyboard is used. Solution for me was to update the constraints after the view has loaded:

override func layoutSubviews() {
    super.layoutSubviews()
    adjustIconWidths()
}
2
votes

Thanks to the great tip by vin047.

In our case I found that the super-call-order issue was the problem.

But with the overall table view (or collection view). Not in the cell as such.

Works:

class UnusualCollectionView: UICollectionView {
    override func layoutSubviews() {
        super.layoutSubviews()
        contentInset = UIEdgeInsets(top: 10, left: 0, bottom: 10, right: 0)
    }

Fails very erratically, particularly on first appearance:

class UnusualCollectionView: UICollectionView {
    override func layoutSubviews() {
        contentInset = UIEdgeInsets(top: 10, left: 0, bottom: 10, right: 0)
        super.layoutSubviews()
    }

The problem caused some profoundly erratic behavior. For example, the 12th cell (no really) was always displaced a long way vertically. Always the 12th cell! Who knows?

vin047 saved the day here, bravo.

2
votes

In my case, I need to assign the height of the cell equal to the height of collectionView. But the height of the collection view depends on the size of the screen. So inside sizeForItemAt delegate

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
    return CGSize(width: currentScreenWidth, height: collectionView.frame.height)
}

To make this work I need to call reloadData function from viewDidLayoutSubviews

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    mycollection.reloadData()
}
0
votes

I had a similar problem, not exactly the same, but could help somebody. I did a override prepareForReuse without calling super.prepareForReuse() in cell. So, when I use UICollectionViewCompositionalLayout with .estimated(100) for cells height, I got weird behaviour on device rotation: if rotate without scrolling, just after view appear, and then scroll - you will have cells with estimated height (100), not updated with the layout. So, try to check this part :

 override func prepareForReuse() {
     super.prepareForReuse()
 }
0
votes

hey guys if still anyone not able to set the content size because the autoresizing cells, you can add this to you collection view and you can get the content size

*here scrolling makes the view get the correct content size so make your CollectionView scrollable or disable it, in both cases you will get the content size after loading the data in cells

`XCollectionView.isScrollEnabled = true/false`

*then reload the data from datasource

`XCollectionView.reloadData()`

*then call layoutneeded

`XCollectionView.layoutIfNedded()`

*then you can check, the content size will be equal to your content

`XCollectionView.collectionViewLayout.collectionViewContentSize.height`

*now set the height of your collection view to the content height and you are good to go