Auto-Sizing Uicollectionview Headers

How to give a collection view section header dynamic height

None of the answers given so far are good practices since calculating the size manually or by rendering a label offscreen are both inefficient and violate the single source of truth principle.

Instances of UICollectionReusableView have the preferredLayoutAttributesFitting() method. When the cell becomes displayed, this method will be called. In this method, if you use Auto Layout, you ask the whole cell for its systemLayoutSizeFitting() and then modify the attributes. The layout will then be responsible to apply them via layout invalidation.

How can I dynamically resize a header view in a UICollectionView?

This is how I solved it. I wish it was more elegant.

  1. Set the UILabel in the header view to have a numberOfLines value of 0. This will let it resize itself.
  2. Move the header out of the storyboard and into a xib.
  3. In collectionView(_:layout:referenceSizeForHeaderInSection:) instantiate a header from the xib. We'll use it to compute a size.
  4. Set the header to the desired width (in my case the width of the collection view), and configure it with the desired text and images
  5. Call setNeedsLayout and layoutIfNeeded.
  6. Use systemLayoutSizeFittingSize(UILayoutFittingCompressedSize) to compute the height.
  7. Return the height and width as CGSize

Notes:

  • The header must be moved out of the storyboard and into a xib because calling dequeuReusableSupplementaryViewOfKind from systemLayoutSizeFittingSize(UILayoutFittingCompressedSize) causes a crash.

  • The header view must be able to compute a correct height only knowing its content and the desired width. This took some fiddling with the constraints.

Self-sizing UICollectionView with UITableView with dynamic header as a cell

To implement auto-sizing collectionView cells, you really only need a few changes.

Couple key points:

  • Cells must satisfy their own constraints. So, instead of calculating sizing in sizeForItemAt, make sure the cells have width and height constraints. These can be dynamic, based on content.

  • Add elements to the collection view cell's contentView, not to the cell itself.

  • For the embedded non-scrolling table view, use a subclass that sets the intrinsic content size height based on the table's contentSize. Example:


final class ContentSizedTableView: UITableView {
override var contentSize:CGSize {
didSet {
invalidateIntrinsicContentSize()
}
}
override var intrinsicContentSize: CGSize {
layoutIfNeeded()
return CGSize(width: UIView.noIntrinsicMetric, height: contentSize.height)
}
}

There is a pretty good tutorial here (not mine): https://medium.com/@andrea.toso/uicollectionviewcell-dynamic-height-swift-b099b28ddd23 that has more detailed explanations.

I implement those concepts in the project you made available, and put it up on a GitHub repo (to make it easy to see the changes): https://github.com/DonMag/ThunderCollectionView

Results:

Sample Image

Sample Image

How to create UICollectionView section Header which autosizes to label content + font size change?

Here is one approach:

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) as! SectionHeaderView

// appearance is not applied until view is added to the view hierarchy, so

// get the UIAppearance protocol for UILabel contained in SectionHeaderView class
let a = UILabel.appearance(whenContainedInInstancesOf: [SectionHeaderView.self])

// update the label(s) font property to the appearance font
headerView.titleLabel.font = a.font
headerView.infoLabel.font = a.font

// Use this view to calculate the optimal size based on the collection view's width
return headerView.systemLayoutSizeFitting(CGSize(width: collectionView.frame.width, height: UIView.layoutFittingCompressedSize.height), withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel)
}


Related Topics



Leave a reply



Submit