I was under the impression that auto sizing cells in UICollectionView had become very simple in iOS 8. So, I'm probably missing something here.
I use a subclass of UICollectionViewFlowLayout
as my layout:
class BuildCollectionViewFlowLayout: UICollectionViewFlowLayout {
required init(coder: NSCoder) {
super.init(coder: coder)
self.minimumLineSpacing = 1
self.sectionInset.top = 20
self.estimatedItemSize = CGSize(width: UIScreen.mainScreen().bounds.width, height: 90)
}
}
Then my ViewController
which is a subclass of UICollectionViewController
looks like this:
class ViewController: UICollectionViewController {
let CellIdentifier = "CellIdentifier"
let apiClient: APIClient()
var builds:Array<JSON>? = []
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.collectionView.registerClass(BuildProjectStatusCollectionViewCell.self, forCellWithReuseIdentifier: CellIdentifier)
self.apiClient.getProjects({ (projects, error) -> Void in
if (error == nil) {
dispatch_async(dispatch_get_main_queue(), {
self.builds = projects
self.collectionView.reloadData()
})
}
else {
}
})
}
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.builds!.count;
}
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
var cell:BuildProjectStatusCollectionViewCell = self.collectionView.dequeueReusableCellWithReuseIdentifier(CellIdentifier, forIndexPath: indexPath) as BuildProjectStatusCollectionViewCell;
cell.setup(self.builds?[indexPath.row])
return cell;
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
And finally my view cell:
class BuildProjectStatusCollectionViewCell : UICollectionViewCell {
var projectNameLabel: UILabel!
var lastCommitMessageLabel: UILabel!
override init(frame: CGRect) {
super.init(frame: frame)
self.initialize()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.initialize()
}
override func awakeFromNib() {
self.initialize()
}
func initialize() {
self.contentView.frame = self.bounds;
self.contentView.autoresizingMask = UIViewAutoresizing.FlexibleWidth | UIViewAutoresizing.FlexibleHeight
self.projectNameLabel = UILabel(forAutoLayout: ())
self.lastCommitMessageLabel = UILabel(forAutoLayout: ())
self.lastCommitMessageLabel.autoresizingMask = UIViewAutoresizing.FlexibleHeight
self.lastCommitMessageLabel.lineBreakMode = NSLineBreakMode.ByWordWrapping
self.lastCommitMessageLabel.numberOfLines = 0
self.contentView.addSubview(self.projectNameLabel)
self.contentView.addSubview(self.lastCommitMessageLabel)
setNeedsUpdateConstraints()
}
func setup(project:JSON!) -> Void {
self.projectNameLabel!.text = project["name"].stringValue
let builds: Array<JSON> = project["builds"].arrayValue!
if builds.count == 1 {
if builds[0]["status"].stringValue == "success" {
self.backgroundColor = UIColor(red: 68/255, green: 175/255, blue: 105/255, alpha: 1.0)
}
else {
self.backgroundColor = UIColor(red: 254/255, green: 57/255, blue: 48/255, alpha: 1.0)
}
self.lastCommitMessageLabel!.text = builds[0]["message"].stringValue
}
}
override func preferredLayoutAttributesFittingAttributes(layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes! {
let attr: UICollectionViewLayoutAttributes = layoutAttributes.copy() as UICollectionViewLayoutAttributes
// Without this, it crashes. AutoLayout constraints playing around. Error: the item width must be less than the width of the UICollectionView minus the section insets left and right values.
return attr;
}
override func updateConstraints() {
super.updateConstraints()
self.projectNameLabel.autoPinEdgesToSuperviewEdgesWithInsets(UIEdgeInsets(top: 5, left: 5, bottom: 5, right: 5), excludingEdge: ALEdge.Bottom)
self.lastCommitMessageLabel.autoPinEdge(ALEdge.Top, toEdge: ALEdge.Bottom, ofView: self.projectNameLabel, withOffset: 10)
self.lastCommitMessageLabel.autoPinEdgeToSuperviewEdge(ALEdge.Leading, withInset: 5)
self.lastCommitMessageLabel.autoPinEdgeToSuperviewEdge(ALEdge.Trailing, withInset: 25)
}
}
The cells gets the size set as the estimatedItemSize
in the layout class.
Am I supposed to do manual calculation of item height in the preferredLayoutAttributesFittingAttributes
method?
If so, doesn't table views support 100% self sizing rows? (http://www.appcoda.com/self-sizing-cells/)
You don't need to override preferredLayoutAttributesFittingAttributes
. And your override doesn't call super, which according to the docs:
The default implementation of this method adjusts the size values to accommodate changes made by a self-sizing cell. Subclasses can override this method and use it to adjust other layout attributes too. If you override this method and want the cell size adjustments, call super first and make your own modifications to the returned attributes.
So that warning you get is legitimate. It's telling you your cell can't be wider than your collection view. And it's probably because you didn't account for inter-item spacing (which by default is > 0) when you set estimatedItemSize
. Try setting a much smaller size. It should work.
And if what you're doing is trying to auto-size only one dimension (ie. always keep a full width), then don't bother. It won't work, as estimatedItemSize
is meant for both dimensions to be resizable.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With