0

I have never seen a topic with so many different answers than I have for setting a dynamic height for a collection view cell. Some of the solutions have unimaginable amounts of code, which only further lends to the consensus that UICollectionView is a cluster since this can be achieved with a UITableView without doing anything since dynamic cell height is built in when the constraints are chained properly. But I want to get a better handle on the collection view. And it's a nightmare.

How do I set the height of a collection view cell to be dynamic to its content size programmatically (no storyboards) in Swift without a hundred lines of code?

You can plug the following into a blank project and it will run clean if you feel so inclined.

CollectionViewFlowLayout:

class CollectionViewFlowLayout: UICollectionViewFlowLayout {

    // inits
    override init() {
        super.init()

        config()
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    // config
    func config() {
        scrollDirection = .vertical
        minimumLineSpacing = 0
        minimumInteritemSpacing = 0
    }
}

CollectionViewController

class CollectionViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout {

    // model properties
    let colorArray: [UIColor] = [.red, .green, .blue, .yellow]
    let labelArray: [String] = [
        "sconewolf sconewolf sconewolf sconewolf sconewolf sconewolf sconewolf sconewolf sconewolf sconewolf sconewolf sconewolf sconewolf sconewolf sconewolf sconewolf sconewolf sconewolf sconewolf sconewolf",
        "sconewolf sconewolf sconewolf sconewolf sconewolf sconewolf sconewolf sconewolf sconewolf sconewolf sconewolf sconewolf",
        "sconewolf sconewolf sconewolf sconewolf sconewolf sconewolf sconewolf sconewolf sconewolf sconewolf sconewolf sconewolf sconewolf sconewolf sconewolf sconewolf sconewolf sconewolf sconewolf sconewolf sconewolf sconewolf sconewolf sconewolf sconewolf sconewolf sconewolf sconewolf sconewolf sconewolf sconewolf sconewolf sconewolf sconewolf",
        "sconewolf sconewolf sconewolf sconewolf sconewolf sconewolf sconewolf sconewolf"
    ]

    // view did load
    override func viewDidLoad() {
        super.viewDidLoad()

        config()
    }

    // config
    func config() {
        collectionView?.dataSource = self
        collectionView?.delegate = self
        collectionView?.backgroundColor = UIColor.gray
        collectionView?.contentInset = UIEdgeInsets(top: -20, left: 0, bottom: 0, right: 0)
        collectionView?.alwaysBounceVertical = true
        collectionView?.alwaysBounceHorizontal = false
        collectionView?.register(CollectionViewCell.self, forCellWithReuseIdentifier: "cell1")
    }

    // data source: number of items in section
    override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return colorArray.count
    }

    // data source: cell for item
    override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {

        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell1", for: indexPath) as! CollectionViewCell
        cell.backgroundColor = colorArray[indexPath.item]
        cell.label.text = labelArray[indexPath.item]
        return cell
    }

    // delegate: size for item
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {

        return CGSize(width: view.frame.width, height: 200)
    }
}

CollectionViewCell

class CollectionViewCell: UICollectionViewCell {

    // view properties
    var label = UILabel()

    // inits
    override init(frame: CGRect) {
        super.init(frame: frame)

        config()
        addLabel()
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    // config
    func config() {
        self.translatesAutoresizingMaskIntoConstraints = false
    }

    // add label
    func addLabel() {
        label.font = UIFont.boldSystemFont(ofSize: 18)
        label.textColor = UIColor.black
        label.numberOfLines = 0
        label.translatesAutoresizingMaskIntoConstraints = false
        addSubview(label)
        label.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
        label.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
        label.widthAnchor.constraint(equalTo: self.widthAnchor).isActive = true
        label.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
        label.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
        label.sizeToFit()
    }
}
Jay Patel
  • 2,642
  • 2
  • 18
  • 40
lurning too koad
  • 2,698
  • 1
  • 17
  • 47

1 Answers1

1

With the help of to this post, I am able to create dynamic UICollectionViewCell programmatically:

Set the estimatedItemSize of layout of UICollectionView like below:

if let flowLayout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout {
    flowLayout.estimatedItemSize = CGSize(width: 1, height: 1)
}

add your UIControls in contentView of UICollectionViewCell

self.contentView.translatesAutoresizingMaskIntoConstraints = false

let screenWidth = UIScreen.main.bounds.size.width

contentView.addSubview(msgLabel)

[
    msgLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 8),
    msgLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 8),
    msgLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -8),
    msgLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -8),
    msgLabel.widthAnchor.constraint(equalToConstant: screenWidth - 16)
    ].forEach{ $0.isActive = true }

Set numberOfLines of UILabel to 0

Jay Patel
  • 2,642
  • 2
  • 18
  • 40