I've read a bunch of posts on adding header to UICollectionView. In an iOS 7+ app in Swift, I'm trying to add a header with a UILabel in it whose height should adjust based on the height of UILabel. The UILabel has lines = 0.
I've set up the header in IB with AutoLayout
The ViewController implements UICollectionViewDelegate, UICollectionViewDataSource
. I didn't set up a custom class for the header but am using these two functions:
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize { //description is a String variable defined in the class let size:CGSize = (description as NSString).boundingRectWithSize(CGSizeMake(CGRectGetWidth(collectionView.bounds) - 20.0, 180.0), options: NSStringDrawingOptions.UsesLineFragmentOrigin, attributes: [NSFontAttributeName: UIFont(name: "Helvetica Neue", size: 16.0)], context: nil).size return CGSizeMake(CGRectGetWidth(collectionView.bounds), ceil(size.height)) } func collectionView(collectionView: UICollectionView!, viewForSupplementaryElementOfKind kind: String!, atIndexPath indexPath: NSIndexPath!) -> UICollectionReusableView! { var reusableview:UICollectionReusableView = UICollectionReusableView() if (kind == UICollectionElementKindSectionHeader) { //listCollectionView is an @IBOutlet UICollectionView defined at class level, using collectionView crashes reusableview = listCollectionView.dequeueReusableSupplementaryViewOfKind(UICollectionElementKindSectionHeader, withReuseIdentifier: "ListHeader", forIndexPath: indexPath) as UICollectionReusableView let label = reusableview.viewWithTag(200) as UILabel //the UILabel within the header is tagged with 200 label.text = description //description is a String variable defined in the class } } return reusableview }
The displaying of the text seems to be working but the height calculation doesn't seem to be working (see screenshot below). Also, I don't think I can access the UILabel via the collectionView...referenceSizeForHeaderInSection
function either. Any suggestions on how to calculate CGSize correctly?
This is how I did it:
let labels = [ "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc ac lorem enim. Curabitur rhoncus efficitur quam, et pretium ipsum. Nam eu magna at velit sollicitudin fringilla nec nec nisi. Quisque nec enim et ipsum feugiat pretium. Vestibulum hendrerit arcu ut ipsum gravida, ut tincidunt justo pellentesque. Etiam lacus ligula, aliquet at lorem vel, ullamcorper commodo turpis. Nullam commodo sollicitudin mauris eu faucibus.", "Lorem ipsum dolor", "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc ac lorem enim. Curabitur rhoncus efficitur quam, et pretium ipsum. Nam eu magna at velit sollicitudin fringilla nec nec nisi. Quisque nec enim et ipsum feugiat pretium."]
The basic idea is to create an identical UILabel
to the one that will be shown in the section header. That label will be used to set the desired size for the header in the referenceSizeForHeaderInSection
method.
I have a label outlet called label
in my UICollectionReusableView
subclass (MyHeaderCollectionReusableView
), which I use for my section header view by assigning it in the storyboard (setting "MyHeader" as Reuse Identifier for the section view). That mentioned label has the horizontal and vertical space constraints to the section header borders in order to autolayout correctly.
override func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int { return 3 } override func collectionView(collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, atIndexPath indexPath: NSIndexPath) -> UICollectionReusableView { let headerView = collectionView.dequeueReusableSupplementaryViewOfKind(kind, withReuseIdentifier: "MyHeader", forIndexPath: indexPath) as MyHeaderCollectionReusableView headerView.label.text = labels[indexPath.section] return headerView } func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize { // that -16 is because I have 8px for left and right spacing constraints for the label. let label:UILabel = UILabel(frame: CGRectMake(0, 0, collectionView.frame.width - 16, CGFloat.max)) label.numberOfLines = 0 label.lineBreakMode = NSLineBreakMode.ByWordWrapping //here, be sure you set the font type and size that matches the one set in the storyboard label label.font = UIFont(name: "Helvetica", size: 17.0) label.text = labels[section] label.sizeToFit() // Set some extra pixels for height due to the margins of the header section. //This value should be the sum of the vertical spacing you set in the autolayout constraints for the label. + 16 worked for me as I have 8px for top and bottom constraints. return CGSize(width: collectionView.frame.width, height: label.frame.height + 16) }
Like the questioner, I had a UICollectionView that contained a header with a single label, whose height I wanted to vary. I created an extension to UILabel
to measure the height of a multiline label with a known width:
public extension UILabel { public class func size(withText text: String, forWidth width: CGFloat) -> CGSize { let measurementLabel = UILabel() measurementLabel.text = text measurementLabel.numberOfLines = 0 measurementLabel.lineBreakMode = .byWordWrapping measurementLabel.translatesAutoresizingMaskIntoConstraints = false measurementLabel.widthAnchor.constraint(equalToConstant: width).isActive = true let size = measurementLabel.systemLayoutSizeFitting(UILayoutFittingCompressedSize) return size } }
Note: the above is in Swift 3 syntax.
Then I implement the header size method of UICollectionViewDelegateFlowLayout
as:
extension MyCollectionViewController : UICollectionViewDelegateFlowLayout { func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize { let text = textForHeader(inSection: section) var size = UILabel.size(withAttributedText: text, forWidth: collectionView.frame.size.width) size.height = size.height + 16 return size } }
The work of calculating the header size is delegated to the above UILabel
extension. The +16
is a experimentally derived fixed offset (8 + 8) that is based on margins and could be obtained programmatically.
All that's needed in the header callback is just to set the text:
override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView { if kind == UICollectionElementKindSectionHeader, let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: headerIdentifier, for: indexPath) as? MyCollectionHeader { let text = textForHeader(inSection: section) headerView.label.text = text return headerView } return UICollectionReusableView() }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With