Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UICollectionView Self sizing Custom cell

Tags:

ios

swift

ios8

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

like image 605
MartinHN Avatar asked Sep 30 '22 13:09

MartinHN


1 Answers

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.

like image 145
hlfcoding Avatar answered Oct 05 '22 23:10

hlfcoding