如何创建自动调整标签内容 + 字体大小更改的 UICollectionView 部分 Header?

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

我想在 UICollectionView 中添加一个 header 部分,其中包含两个标签以显示标题和信息描述。两个标签都可以显示多行文本,header 大小应该自动适应标签内容。

TL;DR

当标签字体更改 appearance() 代理时,自动布局似乎失败。有解决办法吗?


完整描述

这是我在 IB 中设置 header 视图的方式:

可以看出,IB 报告了一个问题。这是一个 “内容优先级歧义”,可以通过将 InfoLabel 的垂直内容拥抱优先级设置为 250 轻松解决。

header 视图添加到 ViewController 中,referenceSizeForHeaderInSection 用于设置大小:

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

到目前为止一切看起来都很好。但是,当使用 appearance() 代理更改标签字体时,它失败了:

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

左边的截图是不改变字体大小的布局,右边的截图是使用UILabel.appearance:

后的布局

字体大小似乎在 计算 header 大小后更新,现在 re-calculation 被触发。

如何解决?

如果两个标签具有相同的拥抱优先级(IB中的错误被简单地忽略),问题几乎是一样的。但是,如果没有明确的信息,自动布局会简单地决定拉伸标题标签而不是信息标签。

这是一种方法:

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