使用自动布局的UICollectionView标题动态高度

jod*_*oda 22 ios autolayout uicollectionview swift

我有UICollectionView类型的标题UICollectionReusableView.
在其中,我有一个标签,其长度因用户输入而异.
因此,我需要根据标签的高度和标头中的其他元素动态调整大小.

这是我的故事板:

故事板

这是我运行应用程序时的结果:

结果

Joe*_*ick 24

这让我绝对疯狂了大约半天.这是最终奏效的.

确保标题中的标签设置为动态调整大小,一行和换行

  1. 将标签嵌入视图中.这将有助于自动调整大小. 在此输入图像描述

  2. 确保标签上的约束是有限的.例如:从底部标签到可重用视图的大于约束将不起作用.见上图.

  3. 在您的子类中添加一个插座,用于嵌入标签的视图

    class CustomHeader: UICollectionReusableView {
        @IBOutlet weak var contentView: UIView!
    }
    
    Run Code Online (Sandbox Code Playgroud)
  4. 使初始布局无效

    override func viewWillLayoutSubviews() {
        super.viewWillLayoutSubviews()
    
        collectionView.collectionViewLayout.invalidateLayout()
    }
    
    Run Code Online (Sandbox Code Playgroud)
  5. 布置标题以获得正确的大小

    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)
        }
    
    }
    
    Run Code Online (Sandbox Code Playgroud)

  • 意识到这不一定适用于更长的列表,因为返回到该列表时标题将不可见。对于较短的列表,此方法仍然有效。 (4认同)
  • 我通过引用控制器中的标题视图来完成这项工作。比你! (2认同)

Moh*_*our 12

斯威夫特3

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

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

  • 这是一个怎样的答案?这里的 self.mylabel 是什么?有什么解释吗? (4认同)

Pim*_*Pim 9

这是一个优雅的,最新的解决方案。

正如其他人所说,首先要确保所有约束都从标题视图的顶部到第一个子视图的顶部,从第一个子视图的底部到第二个子视图的顶部,等等,以及最后一个子视图的底部到标题视图的底部。只有这样,自动布局才能知道如何调整视图的大小。

下列代码片段返回了标题视图的计算大小。

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.layoutFittingExpandedSize.height),
                                              withHorizontalFittingPriority: .required, // Width is fixed
                                              verticalFittingPriority: .fittingSizeLevel) // Height can be as large as needed
}
Run Code Online (Sandbox Code Playgroud)

  • 最佳答案,但请注意,由于插入和边距,systemLayoutSizeFitting 的 collectionView.frame.width 可能会更小...您可以通过使用更精确:letbounds = collectionView.bounds letlayout = collectionView.collectionViewLayout as!UICollectionViewFlowLayout 让宽度=bounds.size.width -layout.sectionInset.left -layout.sectionInset.right -layoutMargins.left -layoutMargins.right (4认同)
  • 看起来这是正确的方法,但是,方法调用中存在一个潜在的错误:“viewForSupplementaryElementOfKind”,它的实现调用“dequeueReusableSupplementaryView()”,这会导致创建 headerView 的另一个实例。由于经常调用“referenceSizeForHeaderInSection”,因此会创建大量 headerView,此外,如果在“sectionHeadersPinToVisibleBounds”设置为“true”的情况下使用流布局,则会出现视觉错误。作为解决方案,标题视图可以是一个惰性属性:`lazy var headerViewToMeasureHeight: UIView = { ... }()` (4认同)
  • 您的解决方案使我的单元格大小正确调整,但是我收到大量 NSLayoutUnsatisfiableConstraint 错误。同一单元格在另一个集合视图中用作“正常”内容单元格,并且在那里它可以正确调整大小。我使用本教程来实现动态单元格大小调整:https://medium.com/@andrea.toso/uicollectionviewcell-dynamic-height-swift-b099b28ddd23 同样,它适用于另一个集合视图中的常规单元格,但不适用于在这个中,如果我将它用作标题单元格。你知道为什么吗? (2认同)
  • 请注意,它无法在 iOS 9/10 上运行。由于在委托调用“collectionView(collectionView:kind:at)”之前,您无法使补充视图出队。它会崩溃。 (2认同)

Ahm*_*d F 5

您可以通过实施以下方法来实现:

ViewController:

class ViewController: UIViewController {
    @IBOutlet weak var collectionView: UICollectionView!

    // the text that you want to add it to the headerView's 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's 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
    }
}
Run Code Online (Sandbox Code Playgroud)

习俗UICollectionReusableView:

class CustomCollectionReusableView: UICollectionReusableView {
    @IBOutlet weak var lblTitle: UILabel!

    override func awakeFromNib() {
        super.awakeFromNib()

        // setup "lblTitle":
        lblTitle.numberOfLines = 0
        lblTitle.lineBreakMode = .byWordWrapping
        lblTitle.sizeToFit() 
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 该解决方案不使用动态高度和自动布局。 (2认同)