Dynamic UICollectionView Header Height Using Auto Layout

I have a UICollectionView with a heading of type UICollectionReusableView .
It has a label, the length of which depends on user input.
Therefore, I need my title to dynamically resize depending on the height of the label and other elements in the title.

This is my storyboard:

storyboard

This is the result when I run the application:

result

+19
source share
4 answers

It drove me crazy for almost half a day. Here is what finally worked.

Make sure the labels in the header have dynamic size, single line and hyphen

  1. Insert your shortcuts into the view. This will help with auto-sizing. enter image description here

  2. Make sure that the limits on your tags are finite. example: limiting more than from the bottom label to the multiple view will not work. See image above.

  3. Add a socket to the subclass for the view in which you have enclosed your shortcuts in

     class CustomHeader: UICollectionReusableView { @IBOutlet weak var contentView: UIView! } 
  4. Wrong source layout

     override func viewWillLayoutSubviews() { super.viewWillLayoutSubviews() collectionView.collectionViewLayout.invalidateLayout() } 
  5. Lay out the title to get the right size

     extension YourViewController: UICollectionViewDelegate { func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize { if let headerView = collectionView.visibleSupplementaryViews(ofKind: UICollectionElementKindSectionHeader).first as? CustomHeader { // Layout to get the right dimensions headerView.layoutIfNeeded() // Automagically get the right height let height = headerView.contentView.systemLayoutSizeFitting(UILayoutFittingExpandedSize).height // return the correct size return CGSize(width: collectionView.frame.width, height: height) } // You need this because this delegate method will run at least // once before the header is available for sizing. // Returning zero will stop the delegate from trying to get a supplementary view return CGSize(width: 1, height: 1) } } 
+24
source

Swift 3

 func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize{ return CGSize(width: self.myCollectionView.bounds.width, height: self.mylabel.bounds.height) } 

https://developer.apple.com/reference/uikit/uicollectionviewdelegateflowlayout/1617702-collectionview

+14
source

You could achieve this by implementing the following:

ViewController:

 class ViewController: UIViewController { @IBOutlet weak var collectionView: UICollectionView! // the text that you want to add it to the headerView label: fileprivate let myText = "Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Nam liber te conscient to factor tum poen legum odioque civiuda." } extension ViewController: UICollectionViewDataSource { func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView { switch kind { case UICollectionElementKindSectionHeader: let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "customHeader", for: indexPath) as! CustomCollectionReusableView headerView.lblTitle.text = myText headerView.backgroundColor = UIColor.lightGray return headerView default: fatalError("This should never happen!!") } } func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return 100 } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "customCell", for: indexPath) cell.backgroundColor = UIColor.brown // ... return cell } } extension ViewController: UICollectionViewDelegateFlowLayout { func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize { // ofSize should be the same size of the headerView label size: return CGSize(width: collectionView.frame.size.width, height: myText.heightWithConstrainedWidth(font: UIFont.systemFont(ofSize: 17))) } } extension String { func heightWithConstrainedWidth(font: UIFont) -> CGFloat { let constraintRect = CGSize(width: UIScreen.main.bounds.width, height: CGFloat.greatestFiniteMagnitude) let boundingBox = self.boundingRect(with: constraintRect, options: NSStringDrawingOptions.usesLineFragmentOrigin, attributes: [NSFontAttributeName: font], context: nil) return boundingBox.height } } 

Custom UICollectionReusableView :

 class CustomCollectionReusableView: UICollectionReusableView { @IBOutlet weak var lblTitle: UILabel! override func awakeFromNib() { super.awakeFromNib() // setup "lblTitle": lblTitle.numberOfLines = 0 lblTitle.lineBreakMode = .byWordWrapping lblTitle.sizeToFit() } } 
+6
source

Here is an elegant, modern solution.

As others have stated, first make sure that you have all the restrictions going from the very top of your header view to the top of the first subview, from the bottom of the first subview to the top of the second subview, etc., and from the bottom of the last subview to the bottom your title submission. Only then can auto-markup learn how to resize your view.

The following code fragment returns the calculated size of the header view.

 func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize { // Get the view for the first header let indexPath = IndexPath(row: 0, section: section) let headerView = self.collectionView(collectionView, viewForSupplementaryElementOfKind: UICollectionView.elementKindSectionHeader, at: indexPath) // Use this view to calculate the optimal size based on the collection view width return headerView.systemLayoutSizeFitting(CGSize(width: collectionView.frame.width, height: UIView.layoutFittingExpandedSize.height), withHorizontalFittingPriority: .required, // Width is fixed verticalFittingPriority: .fittingSizeLevel) // Height can be as large as needed } 
0
source

All Articles