Comment déterminer la hauteur de UICollectionView avec FlowLayout

118

J'ai un UICollectionViewavec un UICollectionViewFlowLayout, et je veux calculer la taille de son contenu (pour le retour intrinsicContentSizenécessaire pour ajuster sa hauteur via AutoLayout).

Le problème est: même si j'ai une hauteur fixe et égale pour toutes les cellules, je ne sais pas combien de "lignes" / lignes j'ai dans le fichier UICollectionView. Je ne peux pas non plus déterminer ce nombre par le nombre d'éléments dans ma source de données, car les cellules représentant les éléments de données varient en largeur, de même que le nombre d'éléments que j'ai dans une ligne du UICollectionView.

Comme je n'ai pas trouvé d'indices sur ce sujet dans la documentation officielle et que la recherche sur Google ne m'a pas amené plus loin, toute aide et idées seraient très appréciées.

Tremblement d'Ignace
la source

Réponses:

254

Whoa! Pour une raison quelconque, après des heures de recherche, j'ai maintenant trouvé une réponse assez simple à ma question: je cherchais complètement au mauvais endroit, en parcourant toute la documentation que je pouvais trouver UICollectionView.

La solution simple et facile réside dans la mise en page sous-jacente: il suffit d'appeler collectionViewContentSizevotre myCollectionView.collectionViewLayoutpropriété et vous obtenez la hauteur et la largeur du contenu au format CGSize. C'est aussi simple que ça.

Tremblement d'Ignace
la source
1
whoa, je souhaite que UITableView ait quelque chose de similaire
Chris Chen
1
Ignatus, @jbandi, Chris et al, pouvez-vous nous en dire plus? Lorsque j'ai testé avec une direction de défilement horizontale avec un grand espacement entre les éléments (afin que les éléments soient disposés horizontalement), la vue de collection a renvoyé une taille de contenu intrinsèque non valide (-1, -1) et collectionViewContentSize a renvoyé ce qui était effectivement les limites de la vue de collection - sans tenir compte du fait qu'il n'y avait qu'une seule ligne entourée d'espace. Bref, ce n'était pas très intrinsèque. Merci!
Chris Conover
8
Quelle est la différence entre collectionViewContentSize et contentSize de la vue de défilement?
rounak
Lorsque vous appelez contentSize de la vue de collection dans viewDidLoad, il renvoie le cadre de la vue de collection, collectionViewContentSize renvoie la taille de contenu correcte.
Fury
1
Pour qui cela peut aider: j'ai dû faire un "layoutIfNeeded ()" avant cela pour que je puisse le faire fonctionner.
asanli
37

Si vous utilisez la mise en page automatique , vous pouvez créer une sous-classe deUICollectionView

Si vous utilisez le code ci-dessous, vous n'avez pas à spécifier de contrainte de hauteur pour la vue de collection car elle varierait en fonction du contenu de la vue de collection.

Ci-dessous est la mise en œuvre:

@interface DynamicCollectionView : UICollectionView

@end

@implementation DynamicCollectionView

- (void) layoutSubviews
{
    [super layoutSubviews];

    if (!CGSizeEqualToSize(self.bounds.size, [self intrinsicContentSize]))
    {
        [self invalidateIntrinsicContentSize];
    }
}

- (CGSize)intrinsicContentSize
{
    CGSize intrinsicContentSize = self.contentSize;

    return intrinsicContentSize;
}

@end
utilisateur1046037
la source
12
n'a pas fonctionné pour moi ... J'utilise ma CollectionView dans un UITableViewCell et j'aimerais que la vue de la table augmente en hauteur à mesure que la vue de la collection augmente en hauteur à l'aide d'iOS8 UITableViewAutomaticDimension Mais ma vue de collection reste petite :(
Georg
Impressionnant. Solution courte et douce à mon UICollectionView à l'intérieur d'un UIScrollView!
dotToString
2
Une chose importante est de désactiver le défilement et les barres de défilement dans la vue de collection
Georg
1
Même problème que Georg, il mesure environ 50 pixels de hauteur alors qu'il devrait être supérieur à 400
xtrinch
3
Oui, je règle la vue de la collection sur "pas de scrollabe, pas de scollbars", puis je la recharge puis le contenu de la tableview est défini. De plus, collectionview est une sous-classe qui renvoie sa taille de contenu sous la forme intrinsicContentSize. J'espère que cela pourra aider!
Georg
25

À viewDidAppearvous pouvez l'obtenir par:

float height = self.myCollectionView.collectionViewLayout.collectionViewContentSize.height;

Peut-être que lorsque vous rechargez des données, vous devez calculer une nouvelle hauteur avec de nouvelles données, vous pouvez l'obtenir en: ajoutez un observateur pour écouter lorsque vos CollectionViewdonnées de rechargement ont terminé à viewdidload:

[self.myCollectionView addObserver:self forKeyPath:@"contentSize" options:NSKeyValueObservingOptionOld context:NULL];

Ensuite, ajoutez la fonction ci-dessous pour obtenir une nouvelle hauteur ou faites quoi que ce soit après le rechargement de collectionview:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary  *)change context:(void *)context
{
    //Whatever you do here when the reloadData finished
    float newHeight = self.myCollectionView.collectionViewLayout.collectionViewContentSize.height;    
}

Et n'oubliez pas de supprimer l'observateur:

[self.myCollectionView removeObserver:self forKeyPath:@"contentSize" context:NULL];
Lee
la source
11

user1046037 réponse dans Swift ...

class DynamicCollectionView: UICollectionView {
    override func layoutSubviews() {
        super.layoutSubviews()
        if bounds.size != intrinsicContentSize() {
            invalidateIntrinsicContentSize()
        }
    }

    override func intrinsicContentSize() -> CGSize {
        return self.contentSize
    }
}
Nick Wood
la source
Cela fonctionne parfaitement mais si vous avez quelque chose comme UILabelci-dessus, le contenu de collectionViewpasser sur un autre élément ci-dessus, avez-vous une autre solution?
KolaCaine
11

Code SWIFT 3 pour la réponse de l'utilisateur1046037

import UIKit

class DynamicCollectionView: UICollectionView {

    override func layoutSubviews() {
        super.layoutSubviews()
        if !__CGSizeEqualToSize(bounds.size, self.intrinsicContentSize) {
            self.invalidateIntrinsicContentSize()
        }

    }

    override var intrinsicContentSize: CGSize {
        return contentSize
    }

}
Mohammad Sadiq Shaikh
la source
Im un plus frais, et je ne sais pas où et comment l'utiliser dans mon ViewController, pouvez-vous s'il vous plaît faire une modification avec la réponse .....?
Abirami Bala
1
il suffit de définir cette classe sur votre collectionview dans Storyboard
Mohammad Sadiq Shaikh
7

Swift 4

class DynamicCollectionView: UICollectionView {
  override func layoutSubviews() {
    super.layoutSubviews()
    if !__CGSizeEqualToSize(bounds.size, self.intrinsicContentSize) {
      self.invalidateIntrinsicContentSize()
    }
  }

  override var intrinsicContentSize: CGSize {
    return collectionViewLayout.collectionViewContentSize
  }
}
Wilson
la source
1
J'ai trouvé que cela ne fonctionnait pas dans l'iPhone 6/7 plus et XR, XS-MAX
Rahul K Rajan
7

Swift 4.2

class DynamicCollectionView: UICollectionView {
    override func layoutSubviews() {
        super.layoutSubviews()
        if bounds.size != intrinsicContentSize {
            invalidateIntrinsicContentSize()
        }
    }

    override var intrinsicContentSize: CGSize {
        return self.contentSize
    }
}
Cesare
la source
4

Il est trop tard pour répondre à cette question, mais je suis récemment passé par ce problème.
La réponse ci - dessus de @ lee m'aide beaucoup à obtenir ma réponse. Mais cette réponse se limite à l' objectif-c et je travaillais rapidement .
@lee En référence à votre réponse, permettez-moi de laisser l'utilisateur rapide résoudre ce problème facilement. Pour cela, veuillez suivre les étapes ci-dessous:

Déclarez une CGFloatvariable dans la section déclaration:

var height : CGFloat!

À viewDidAppearvous pouvez l'obtenir par:

height = self.myCollectionView.collectionViewLayout.collectionViewContentSize().height

Peut-être que lorsque vous avez reloadbesoin de calculer une nouvelle hauteur avec de nouvelles données, vous pouvez l'obtenir par: addObserverpour écouter lorsque vos CollectionViewdonnées de rechargement ont terminé à viewWillAppear:

override func viewWillAppear(animated: Bool) {
        super.viewWillAppear(true)
        ....
        ....
        self.shapeCollectionView.addObserver(self, forKeyPath: "contentSize", options: NSKeyValueObservingOptions.Old, context: nil)
    }

Ensuite, ajoutez la fonction ci-dessous pour obtenir une nouvelle hauteur ou faites quelque chose après le collectionviewrechargement terminé:

override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
        let newHeight : CGFloat = self.myCollectionView.collectionViewLayout.collectionViewContentSize().height

        var frame : CGRect! = self.myCollectionView.frame
        frame.size.height = newHeight

        self.myCollectionView.frame = frame
    }

Et n'oubliez pas de supprimer l'observateur:

override func viewWillDisappear(animated: Bool) {
    super.viewWillDisappear(true)
    ....
    ....
    self.myCollectionView.removeObserver(self, forKeyPath: "contentSize")
}

J'espère que cela vous aidera à résoudre votre problème rapidement.

Euh. Vihar
la source
@ShikhaSharma: Vous pouvez ajouter le code removeObserver dans la méthode "ViewDidDisappear", qui supprimera l'observateur uniquement après que la vue aura confirmé sa disparition. Veuillez vérifier la réponse mise à jour.
Euh. Vihar
J'obtiens cette erreur: Un -observeValueForKeyPath: ofObject: change: context: message a été reçu mais pas traité.
Shikha Sharma
@ShikhaSharma: Désolé, c'est mon erreur de spécifier la méthode. Essayez de mettre le code removeobserver dans viewWillDisappear.
Euh. Vihar
1

Cette réponse est basée sur @Er. La réponse de Vihar. Vous pouvez facilement implémenter la solution en utilisant RxSwift

     collectionView.rx.observe(CGSize.self , "contentSize").subscribe(onNext: { (size) in
        print("sizer \(size)")
    }).disposed(by: rx.disposeBag)
HUNG TRAN DUY
la source
0

Version Swift de @ user1046037:

public class DynamicCollectionView: UICollectionView {

    override public func layoutSubviews() {
        super.layoutSubviews()
        if !bounds.size.equalTo(intrinsicContentSize) {
            invalidateIntrinsicContentSize()
        }
    }

    override public var intrinsicContentSize: CGSize {
        let intrinsicContentSize: CGSize = contentSize
        return intrinsicContentSize
    }
}
Joshua Hart
la source
0

Swift 4.2, Xcode 10.1, iOS 12.1:

Pour une raison quelconque, collectionView.contentSize.heightapparaissait plus petit que la hauteur résolue de ma vue de collection. Tout d'abord, j'utilisais une contrainte de mise en page automatique relative à la moitié de la hauteur de la vue supervisée. Pour résoudre ce problème, j'ai changé la contrainte pour qu'elle soit relative à la "zone sûre" de la vue.

Cela m'a permis de définir la hauteur de cellule pour remplir ma vue de collection en utilisant collectionView.contentSize.height:

private func setCellSize() {
    let height: CGFloat = (collectionView.contentSize.height) / CGFloat(numberOfRows)
    let width: CGFloat = view.frame.width - CGFloat(horizontalCellMargin * 2)

    let layout = collectionView.collectionViewLayout as! UICollectionViewFlowLayout
    layout.itemSize = CGSize(width: width, height: height)
}

Avant

contrainte de hauteur relative à la vue supervisée mauvais résultat du simulateur

Après

contrainte de hauteur par rapport à la zone de sécurité bon résultat du simulateur

Andrew Kirna
la source
0

en supposant que votre collectionView est nommée collectionView, vous pouvez prendre la hauteur comme suit.

let height = collectionView.collectionViewLayout.collectionViewContentSize.height
Pramodya Abeysinghe
la source
0

De plus, vous feriez mieux d'appeler reloadDataavant d'obtenir la hauteur:

theCollectionView.collectionViewLayout.collectionViewContentSize;
guozqzzu
la source