CollectionView dynamic height with Swift 3 in iOS

Provide estimatedSize to your UICollectionViewLayout.

The estimated size should be the size shown in the size inspector of your xib.

collectionViewLayout.estimatedItemSize = CGSize(width: collectionView.frame.width, height: 50)

Override the method preferredLayoutAttributesFitting(_:) in your UICollectionViewCell

override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
    setNeedsLayout()
    layoutIfNeeded()
        
    let size = contentView.systemLayoutSizeFitting(layoutAttributes.size)
        
    var frame = layoutAttributes.frame
    frame.size.height = ceil(size.height)
        
    layoutAttributes.frame = frame
        
    return layoutAttributes
}

Your collection view cell will now have dynamic size as per the content.


all what you need to do is making an IBOutlet for the UICollectionView layout, set the estimatedItemSize to any size, in your cell class you have to specify the cell width (if you just want the height to be dynamic and the width is static) and/or height in awakeFromNib and disable translatesAutoresizingMaskIntoConstraintsself.contentView.translatesAutoresizingMaskIntoConstraints = false. so the final result should be something like this

class ProfileViewController: UIViewController {
    @IBOutlet var collectionView: UICollectionView!
    @IBOutlet var collectionLayout: UICollectionViewFlowLayout!


    var person: Person!

override func viewDidLoad() {
        super.viewDidLoad()

        collectionView.register(UINib(nibName: "ProfileCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "Cell")
        collectionLayout.estimatedItemSize = CGSize(width: screenWidth * 0.4, height: 1)
        collectionView.reloadData()
    }

    func numberOfSections(in collectionView: UICollectionView) -> Int {
        return 1
    }

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return 1
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! ProfileCollectionViewCell

        return cell
    }
    }

and your cell class should be something like this

class ProfileCollectionViewCell: UICollectionViewCell {
@IBOutlet var containerWidthConstraint: NSLayoutConstraint!
 override func awakeFromNib() {
        super.awakeFromNib()

        self.contentView.translatesAutoresizingMaskIntoConstraints = false
        containerWidthConstraint.constant = screenWidth - (2 * 12)
    }
    }

and this is a good tutorial for self sizing in ios https://engineering.shopspring.com/dynamic-cell-sizing-in-uicollectionview-fd95f614ef80


Use the following code to change the height according to the text displayed:

 func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
    return 0
}
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
    return CGSizeMake(view.frame.width , 64)
}
override func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize 
{
  let approximateWidthOfContent = view.frame.width - x
    // x is the width of the logo in the left 

  let size = CGSize(width: approximateWidthOfContent, height: 1000)

  //1000 is the large arbitrary values which should be taken in case of very high amount of content 

 let attributes = [NSFontAttributeName: UIFont.systemFont(ofSize: 15)]            
 let estimatedFrame = NSString(string: user.bioText).boundingRect(with: size, options: .usesLineFragmentOrigin, attributes: attributes, context: nil)
 return CGSize(width: view.frame.width, height: estimatedFrame.height + 66)          
 }

I solved it :)

I just tried to compute my cell's width and expect height in sizeForItemAt function and it works !

Code below :

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
    let imageShowingWidth: CGFloat = self.view.frame.width / CGFloat(self.howManyImageShowing)

    let labelName = "@\(self.tweetShowing[indexPath.row].user.screenName) (\(self.tweetShowing[indexPath.row].user.name))"
    let labelNameFont: UIFont = UIFont(name: "PingFangTC-Semibold", size: 16)!
    let labelNameWidth: CGFloat = self.view.frame.width - YourWidthOffSet// (YourWidthOffSet include all images' width and all margins)
    let labelNameHeight: CGFloat = self.getHeightForLable(labelWidth: labelNameWidth, labelText: labelName, labelFont: labelNameFont)

    let labelContentFont: UIFont = UIFont(name: "PingFangTC-Regular", size: 16)!
    let labelContentHeight: CGFloat = self.getHeightForLable(labelWidth: labelNameWidth, numberOfLines: 0, labelText: self.tweetShowing[indexPath.row].text, labelFont: labelContentFont)

    let cellHeight: CGFloat = labelNameHeight + labelContentHeight + YourHeightOffSet // (YourHeightOffSet means all margins)

    return self.typeControl.selectedSegmentIndex == 0 ? CGSize(width: self.view.frame.width, height: cellHeight) : CGSize(width: imageShowingWidth, height: imageShowingWidth)
}

func getHeightForLable(labelWidth: CGFloat, numberOfLines: Int = 1, labelText: String, labelFont: UIFont) -> CGFloat {
    let tempLabel: UILabel = UILabel(frame: CGRect(x: 0, y: 0, width: labelWidth, height: CGFloat.greatestFiniteMagnitude))
    tempLabel.numberOfLines = numberOfLines
    tempLabel.text = labelText
    tempLabel.font = labelFont
    tempLabel.sizeToFit()
    return tempLabel.frame.height
}