Andrei Herford
Andrei Herford

Reputation: 18745

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

I would like to add a section header to a UICollectionView which contains two lables to show a title and a info description. Both labels can show multi line texts and the header size should be automatically adopted to the label content.

TL;DR

Auto layout seems to fails when the label font is changed the appearance() proxy. Is there solution to solve this?


Complete description

This is how I set up the header view in IB:

enter image description here

As one can see, IB reports a problem. This is a "Content Priority Ambiguity" which can easily be solved by setting the Vertical Content Hugging Priority of InfoLabel to 250.

The header view is than added in the ViewController and referenceSizeForHeaderInSection is used to setup the size:

func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
    if kind == UICollectionView.elementKindSectionHeader {
        let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "header", for: indexPath) as? SectionHeaderView ?? SectionHeaderView()
        
        headerView.titleLabel.text = "Title Label with long text. More text..."
        headerView.infoLabel.text = "Info Label with long text. More text...."
        
        return headerView
    }
    
    return UICollectionReusableView()
}

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's width
    return headerView.systemLayoutSizeFitting(CGSize(width: collectionView.frame.width, height: UIView.layoutFittingCompressedSize.height), withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel)
}

So far everything looks good. However, when the label font is changed using the appearance() proxy, it fails:

// called in application:didFinishLaunchingWithOptions
UILabel.appearance(whenContainedInInstancesOf: [SectionHeaderView.self]).font = UIFont.systemFont(ofSize: 10)

The left screenshot shows the layout without the changing the font size, the right screenshot the layout after using UILabel.appearance:

It seems that the font size is updated after the header size was calculated and that now re-calculation is triggered.

How can this be solved?

If both labels have the same hugging priority (error in IB is simply ignored), the problem is almost the same. However, without explicit information auto layout simply decides that the title label is stretched instead of the info label.

enter image description here

Upvotes: 2

Views: 1268

Answers (1)

DonMag
DonMag

Reputation: 77638

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

Upvotes: 1

Related Questions