web-dev-qa-db-fra.com

Hauteur dynamique de l'en-tête UICollectionView à l'aide de la disposition automatique

J'ai UICollectionView avec un en-tête de type UICollectionReusableView.
J'y ai une étiquette dont la longueur varie en fonction de l'entrée utilisateur.
Par conséquent, j'ai besoin que mon en-tête soit redimensionné dynamiquement en fonction de la hauteur de l'étiquette et des autres éléments de l'en-tête.

Voici mon storyboard:

storyboard

Voici le résultat lorsque j'exécute l'application:

result

19
joda

Cela m'a rendu complètement fou pendant environ une demi-journée. Voici ce qui a finalement fonctionné.

Assurez-vous que les étiquettes dans votre en-tête sont définies pour être dimensionnées dynamiquement, une ligne et un habillage

  1. Intégrez vos étiquettes dans une vue. Cela vous aidera à redimensionner automatiquement. enter image description here

  2. Assurez-vous que les contraintes sur vos étiquettes sont finies. ex: une contrainte supérieure à celle de l'étiquette inférieure à la vue réutilisable ne fonctionnera pas. Voir l'image ci-dessus.

  3. Ajoutez une sortie à votre sous-classe pour la vue dans laquelle vous avez incorporé vos étiquettes

    class CustomHeader: UICollectionReusableView {
        @IBOutlet weak var contentView: UIView!
    }
    
  4. Invalider la disposition initiale

    override func viewWillLayoutSubviews() {
        super.viewWillLayoutSubviews()
    
        collectionView.collectionViewLayout.invalidateLayout()
    }
    
  5. Disposez l'en-tête pour obtenir la bonne taille

    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)
        }
    
    }
    
24
Joe Susnick

Swift 3

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

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

14
Mohammad Razipour

Vous pouvez y parvenir en implémentant les éléments suivants:

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

Le UICollectionReusableView personnalisé:

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

    override func awakeFromNib() {
        super.awakeFromNib()

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

Voici une solution élégante et à jour.

Comme cela a été dit par d'autres, assurez-vous d'abord que toutes vos contraintes s'exécutent du haut de votre vue d'en-tête au haut de la première sous-vue, du bas de la première sous-vue au haut de la deuxième sous-vue, etc., et à partir de la du bas de la dernière sous-vue au bas de votre vue d'en-tête. Ce n'est qu'alors que la mise en page automatique peut savoir comment redimensionner votre vue.

Le code suivant extrait renvoie la taille calculée de votre vue d'en-tête.

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