Subview frame is incorrect when creating UICollectionViewCell
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()
.
Related videos on Youtube
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, 2022Comments
-
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 over 9 yearsHow 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 over 9 yearsHey @CharlieWu, I am indeed using auto layout... colorView and nameLabel are fully constrained...
-
-
Felipe Ferri about 9 yearsWhere 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 almost 8 yearsThe first code sample here is finally the true answer. Thank you.
-
Felipe Ferri over 7 yearsI had the same problem again and your answer was better than mine. :-)
-
Alejandro Iván over 7 yearsSidenote:
- (void)awakeFromNib { [super awakeFromNib]; [self layoutIfNeeded]; /* frames already calculated here */ }
-
Mohammad Zaid Pathan over 7 yearsSame solution for
UITableViewCell
as well. -
Vadim Bulavin almost 7 years@Magnas in UICollectionViewCell subclass.
-
inokey almost 7 yearsBrilliant! Thank you.
-
JerryZhou over 6 yearsBest place to add the logics for my customize view.
-
adamF almost 6 yearsThis was the only thing that helped in my case, thanks!
-
Nike Kov about 5 years
layoutSubviews()
didn't help me. Only this helped. Check this article too medium.com/@abhimuralidharan/…. -
Rakesha Shastri almost 4 years@Fattie for me
layoutIfNeeded
is not even being called without explicitly calling it incellForRowAt
for aUICollectionViewCell
. Any clue why? -
Nicolai Harbo about 3 yearsLifesaver! Thanks. Had been messing around with layoutSubviews() etc. with no luck - this works as intended!