Динамическая высота заголовка UICollectionView с использованием Auto Layout

У меня есть UICollectionView с заголовком типа UICollectionReusableView.
В нем есть метка, длина которой зависит от пользовательского ввода.
Следовательно, мне нужно, чтобы мой заголовок динамически изменял размеры в зависимости от высоты надписи и других элементов в заголовке.

Это моя раскадровка:

storyboard

Это результат, когда я запускаю приложение:

result

Ответ 1

Это сводило меня с ума почти полдня. Вот что наконец сработало.

Убедитесь, что метки в заголовке имеют динамический размер, одну строку и перенос

  1. Вставьте ваши ярлыки в представление. Это поможет с авторазмером. enter image description here

  2. Убедитесь, что ограничения на ваши метки конечны. пример: ограничение больше, чем от нижней метки до многоразового представления, не будет работать. Смотрите изображение выше.

  3. Добавьте в свой подкласс розетку для представления, в которое вы вложили свои ярлыки в

    class CustomHeader: UICollectionReusableView {
        @IBOutlet weak var contentView: UIView!
    }
    
  4. Неправильный исходный макет

    override func viewWillLayoutSubviews() {
        super.viewWillLayoutSubviews()
    
        collectionView.collectionViewLayout.invalidateLayout()
    }
    
  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)
        }
    
    }
    

Ответ 3

Вы могли бы достичь этого путем реализации следующего:

ViewController:

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

    // the text that you want to add it to the headerView 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 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
    }
}

Пользовательский UICollectionReusableView:

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

    override func awakeFromNib() {
        super.awakeFromNib()

        // setup "lblTitle":
        lblTitle.numberOfLines = 0
        lblTitle.lineBreakMode = .byWordWrapping
        lblTitle.sizeToFit() 
    }
}

Ответ 4

Здесь элегантное, современное решение.

Как заявили другие, сначала убедитесь, что у вас есть все ограничения, идущие от самой вершины вашего представления заголовка до вершины первого подпредставления, от нижней части первого подпредставления до вершины второго подпредставления и т.д., И от нижней части последнего подпредставления в нижней части вашего представления заголовка. Только тогда авторазметка сможет узнать, как изменить размер вашего представления.

Следующий фрагмент кода возвращает рассчитанный размер представления заголовка.

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