Subview frame is incorrect when creating UICollectionViewCell

Felipe Ferri picture Felipe Ferri · Feb 2, 2015 · Viewed 21.1k times · Source

The problem

I created a UICollectionViewController with a custom UICollectionViewCell. The custom cell contains a large and rectangular UIView (named colorView) and a UILabel (named nameLabel).

When the collection is first populated with its cells and I print colorView.frame, the printed frames have incorrect values. I know they are incorrect, because the colorView frames are larger than the cell frame themselves, even though the colorView gets drawn correctly.

However, if I scroll the collectionView enough to trigger a reuse of a previously created cell, the colorView.frame now has correct values! I need the correct frames because I want to apply rounded corners to the colorView layer and I need the correct coloView size in order to do this. By the way, in case you are wondering, colorView.bounds also has the same wrong size value as the colorView.frame.

The question

Why are the frames incorrect when creating the cells?

And now some code

This is my UICollectionViewCell:

class BugCollectionViewCell: UICollectionViewCell {
    @IBOutlet weak var colorView: UIView!
    @IBOutlet weak var nameLabel: UILabel!
}

and this is the UICollectionViewController:

import UIKit

let reuseIdentifier = "Cell"
let colors = [UIColor.redColor(), UIColor.blueColor(),
              UIColor.greenColor(), UIColor.purpleColor()]
let labels = ["red", "blue", "green", "purple"]

class BugCollectionViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout {
    override func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
        return 1
    }

    override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return colors.count
    }

    override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as BugCollectionViewCell

        println("ColorView frame: \(cell.colorView.frame) Cell frame: \(cell.frame)")

        cell.colorView.backgroundColor = colors[indexPath.row]
        cell.nameLabel.text = labels[indexPath.row]

        return cell
    }

    func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
        let width = self.collectionView?.frame.width
        let height = self.collectionView?.frame.height
        return CGSizeMake(width!, height!/2)
    }
}

The collection view is setup in order to show two cells at a time, vertically, each cell containing a large rectangle painted with a color and a label with the color name.

When I just run the above code on the simulator, I get the following printed result:

ColorView frame: (0.0,0.0,320.0,568.0) Cell frame: (0.0,0.0,375.0,333.5)
ColorView frame: (0.0,0.0,320.0,568.0) Cell frame: (0.0,343.5,375.0,333.5)

It is a weird result - colorView.frame has a height of 568 points, while the cell frame is only 333.5 points tall.

If I drag the collectionView down and a cell gets reused, the following result is printed:

ColorView frame: (8.0,8.0,359.0,294.0) Cell frame: (0.0,1030.5,375.0,333.5)
ColorView frame: (8.0,8.0,359.0,294.0) Cell frame: (0.0,343.5,375.0,333.5)

Something, which I can’t understand, happened along the way that corrects the frame of colorView.

I think it has something to do with the fact that the cell is loaded from the Nib, so instead of using the init(frame: frame) initializer the controller uses the init(coder: aCoder) initializer, so as soon as the cell is created it probably comes with some default frame which I can't edit anyhow.

I’ll appreciate any help that allows me to understand what is happening!

I am using Xcode 6.1.1. with the iOS SDK 8.1.

Answer

Jon Vogel picture Jon Vogel · Jun 11, 2015

You can get the final frames of your cell by overriding layoutIfNeeded() in your custom Cell class like this:

override func layoutIfNeeded() {
    super.layoutIfNeeded()
    self.subView.layer.cornerRadius = self.subView.bounds.width / 2
}

then in your UICollectionView data Source method cellForRowAtIndexPath: do this:

let cell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell", forIndexPath: indexPath) as! CustomCollectionViewCell
cell.setNeedsLayout()
cell.layoutIfNeeded()