CollectionView dynamic height with Swift 3 in iOS

32,915

Solution 1

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

Solution 2

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.

Solution 3

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
}

Solution 4

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

Share:
32,915
Kevin
Author by

Kevin

Updated on July 14, 2022

Comments

  • Kevin
    Kevin almost 2 years

    Recently, I'm trying to do some projects for practicing. So I watched the course "Developing Apps for iOS", Stanford University, CS193P, 2017.

    And now, I'm doing "Smashtag" project but I have some problems in it. I want to use a CollectionView with two UICollectionViewFlowLayout to show two types in each xib (one is like tableView, another is showing a image in square) of CollectionView by SegmentedControl.

    My problems below :

    1. How to make CollectionViewCell showing in dynamic height just like TableView's UITableViewAutomaticDimension?

      And this is what I just tried below :

      I tried to make CollectionView's autoResize setting as like TableView's autoDimension with using UICollectionViewFlowLayoutAutomaticSize. ( @available(iOS 10.0, *) )

    enum CollectionViewType {
      case tweet
      case image
    }
    
    // MARK: - Decide What Kind Of FlowLayout
    func decideFlowLayout(type: CollectionViewType) -> UICollectionViewFlowLayout {
    
        // just for .image type
        var howManyImageShowing = 3  
        var imageShowingWidth: Double {
            return Double(self.view.frame.width) / Double(howManyImageShowing)
        }
    
        // .tweet is a type showing like TableViewCell -> this make me confused !!
        // another type is just showing a image in square -> I have no problem here
        let estimatedItemSize = type == .tweet ? CGSize(width: self.view.frame.width, height: 155.0) :
                                                    CGSize(width: imageShowingWidth, height: imageShowingWidth)
    
        let collectionViewLayout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
        collectionViewLayout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
    
        // --------- Make this setting as TableView does --------- 
        // ------------ Somethis just like this below ------------
        /*
            tableView.estimatedRowHeight = 155.0
            tableView.rowHeight = UITableViewAutomaticDimension
        */
    
        collectionViewLayout.estimatedItemSize = estimatedItemSize
        collectionViewLayout.itemSize = UICollectionViewFlowLayoutAutomaticSize
        // ------------------------------------------------------- 
    
        collectionViewLayout.minimumLineSpacing = 0
        collectionViewLayout.minimumInteritemSpacing = 0
    
        return collectionViewLayout
    }
    

    Constraint of my CollectionViewCell xib here : enter image description here But it has bug when I run this app

    I don't know what I miss or misunderstand. enter image description here

    1. Why this solution will work ?

      I already search so many solutions of this problem in Stack Overflow but I just find this and make me really confused :(

      This solution give the view a Width Constraint which is in CollectionViewCell xib and set this constraint's constant a value and it works! I can not configure out why Width Constraint will make the cell's height in dynamic? This make me really confused, I think this is weird...

      https://github.com/tttsunny/CollectionViewAutoSizingTest

    class Cell: UICollectionViewCell {
        @IBOutlet weak var headerLabel: UILabel!
        @IBOutlet weak var descriptionLabel: UILabel!
        @IBOutlet weak var widthConstraint: NSLayoutConstraint!
    
        override func awakeFromNib() {
            super.awakeFromNib()
            // Initialization code
            self.contentView.translatesAutoresizingMaskIntoConstraints = false
            let screenWidth = UIScreen.main.bounds.size.width
            widthConstraint.constant = screenWidth - (2 * 12)
        }
    }
    
    1. How to do CollectionViewCell dynamic height without using UICollectionViewFlowLayoutAutomaticSize. ( @available(iOS 10.0, *) ) ?

      Because I want to handle different iOS Versions not only in iOS 10 but also below iOS 10.

    I really want to be pro in iOS development and a great developer but I'm still learning and trying. Any replies I will be greatly appreciated.

    Thank you !!