Subview frame is incorrect when creating UICollectionViewCell

23,992

Solution 1

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

Solution 2

I had the same issue with a UICollectionViewCell using auto layout constraints.

I had to call layoutIfNeeded before I was configuring my subview that relied on the views frame width.

Solution 3

Had this issue with Core Graphics drawing in iOS 10, Swift 3.0.1.

Add this method to UICollectionView subclass:

override func didMoveToSuperview() {
    super.didMoveToSuperview()
    setNeedsLayout()
    layoutIfNeeded()
}

My problem was that Core Graphics shapes were not calculated properly, because a layoutSubviews() wasn't called.

Solution 4

Ok, I understand now that the cell is created before auto layout defines its frames. That is the reason why at the moment of creation the bounds are wrong. When the cells are reused the frames have been already corrected.

I was having this problem while creating a custom UIView that placed some layers and subviews in specific coordinates. When instances of this UIView were created, the placement of the subviews were all wrong (because auto layout hadn't kick off yet).

I found out that instead of configuring the view subviews on init(coder: aCoder) I had to override the method layoutSubviews(). This is called when auto layout asks each view to layout its own subviews, so at this point at least the parent view has the correct frame and I can use it for laying the subviews correctly.

Probably if I had used constraints on the custom view code instead of dealing myself with frame sizes and positioning then the layout would have been done properly and it wouldn't be necessary to override layoutSubviews().

Share:
23,992

Related videos on Youtube

Felipe Ferri
Author by

Felipe Ferri

As an engineer I worked for 6 years in the aeronautical industry. In 2013 I quit my job in order to focus on my ideas. I started a small software development agency, working mostly with freelancers, and am glad of being able to work from my home in the countryside. I've been programing for a looong time. I prefer coding in Python and Swift, but have experience in assembly, C, C++, Matlab and Objective C. Right now I am learning and experimenting with Django and NativeScript.

Updated on July 09, 2022

Comments

  • Felipe Ferri
    Felipe Ferri almost 2 years

    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.

    • Charlie Wu
      Charlie Wu over 9 years
      How do you position ColorView? You are not using autolayout right? I think what's happening to you is the view layout is loaded from the nib and translated to autolayout. But your storyboard viewcontroller size is different to the simulator. Use autolayout to position your views and things should work right then.
    • Felipe Ferri
      Felipe Ferri over 9 years
      Hey @CharlieWu, I am indeed using auto layout... colorView and nameLabel are fully constrained...
  • Felipe Ferri
    Felipe Ferri about 9 years
    Where exactly do you call layoutIfNeeded? I remember I tried to call it on the view controller init but it didn't work, seemed as if the cells were loaded before the auto layout was executed, even after queuing with layoutIfNeeded...
  • Fattie
    Fattie almost 8 years
    The first code sample here is finally the true answer. Thank you.
  • Felipe Ferri
    Felipe Ferri over 7 years
    I had the same problem again and your answer was better than mine. :-)
  • Alejandro Iván
    Alejandro Iván over 7 years
    Sidenote: - (void)awakeFromNib { [super awakeFromNib]; [self layoutIfNeeded]; /* frames already calculated here */ }
  • Mohammad Zaid Pathan
    Mohammad Zaid Pathan over 7 years
    Same solution for UITableViewCell as well.
  • Vadim Bulavin
    Vadim Bulavin almost 7 years
    @Magnas in UICollectionViewCell subclass.
  • inokey
    inokey almost 7 years
    Brilliant! Thank you.
  • JerryZhou
    JerryZhou over 6 years
    Best place to add the logics for my customize view.
  • adamF
    adamF almost 6 years
    This was the only thing that helped in my case, thanks!
  • Nike Kov
    Nike Kov about 5 years
    layoutSubviews() didn't help me. Only this helped. Check this article too medium.com/@abhimuralidharan/….
  • Rakesha Shastri
    Rakesha Shastri almost 4 years
    @Fattie for me layoutIfNeeded is not even being called without explicitly calling it in cellForRowAt for a UICollectionViewCell. Any clue why?
  • Nicolai Harbo
    Nicolai Harbo about 3 years
    Lifesaver! Thanks. Had been messing around with layoutSubviews() etc. with no luck - this works as intended!