Cellules à dimensionnement automatique UICollectionView avec disposition automatique

207

J'essaie de faire UICollectionViewCellsfonctionner le dimensionnement automatique avec la mise en page automatique, mais je n'arrive pas à faire en sorte que les cellules s'adaptent au contenu. J'ai du mal à comprendre comment la taille de la cellule est mise à jour à partir du contenu de ce qui se trouve dans le contenu de la cellule.

Voici la configuration que j'ai essayée:

  • Personnalisé UICollectionViewCellavec un UITextViewdans son contentView.
  • Le défilement pour UITextViewest désactivé.
  • La contrainte horizontale de contentView est: "H: | [_textView (320)]", c'est-à-dire qu'elle UITextViewest épinglée à gauche de la cellule avec une largeur explicite de 320.
  • La contrainte verticale de contentView est: "V: | -0 - [_ textView]", c'est-à-dire UITextViewépinglé en haut de la cellule.
  • Le UITextViewa une contrainte de hauteur définie sur une constante à laquelle les UITextViewrapports s'adapteront au texte.

Voici à quoi cela ressemble avec l'arrière-plan de la cellule défini sur rouge et l' UITextViewarrière - plan défini sur bleu: fond de cellule rouge, UITextView fond bleu

J'ai mis le projet avec lequel je jouais sur GitHub ici .

abeille
la source
Mettez-vous en œuvre la méthode sizeForItemAtIndexPath?
Daniel Galasko
@DanielGalasko Je ne le suis pas. Je crois comprendre que la nouvelle fonctionnalité de cellules à dimensionnement automatique dans iOS 8 ne vous oblige plus à le faire.
rawbee
Je ne sais pas d'où vous avez obtenu ces informations, mais vous devez toujours le faire, jetez un œil à l'extrait de la WWDC asciiwwdc.com/2014/sessions/226
Daniel Galasko
mais encore une fois cela dépend de votre cas d'utilisation, assurez-vous que vous implémentez estimationItemSize
Daniel Galasko
1
2019 - Il est essentiel d'aller voir ceci: stackoverflow.com/a/58108897/294884
Fattie

Réponses:

309

Mis à jour pour Swift 5

preferredLayoutAttributesFittingAttributesrenommé preferredLayoutAttributesFittinget utiliser le dimensionnement automatique


Mis à jour pour Swift 4

systemLayoutSizeFittingSize renommé en systemLayoutSizeFitting


Mis à jour pour iOS 9

Après avoir vu ma solution GitHub casser sous iOS 9, j'ai finalement eu le temps d'examiner le problème de manière approfondie. J'ai maintenant mis à jour le référentiel pour inclure plusieurs exemples de configurations différentes pour les cellules à dimensionnement automatique. Ma conclusion est que les cellules à dimensionnement automatique sont excellentes en théorie mais en désordre dans la pratique. Un mot de prudence lors de l'utilisation de cellules à dimensionnement automatique.

TL; DR

Découvrez mon projet GitHub


Les cellules à dimensionnement automatique ne sont prises en charge qu'avec la disposition de flux, assurez-vous donc que c'est ce que vous utilisez.

Il y a deux choses que vous devez configurer pour que les cellules à dimensionnement automatique fonctionnent.

1. Ensemble estimatedItemSizesurUICollectionViewFlowLayout

La disposition du flux deviendra de nature dynamique une fois que vous aurez défini la estimatedItemSizepropriété.

self.flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize

2. Ajoutez un support pour le dimensionnement de votre sous-classe de cellules

Cela vient en 2 saveurs; Disposition automatique ou remplacement personnalisé de preferredLayoutAttributesFittingAttributes.

Créer et configurer des cellules avec la disposition automatique

Je n'entrerai pas dans les détails à ce sujet car il y a un article SO brillant sur la configuration des contraintes pour une cellule. Méfiez-vous simplement que Xcode 6 a cassé un tas de choses avec iOS 7 donc, si vous prenez en charge iOS 7, vous devrez faire des choses comme s'assurer que le masque de redimensionnement automatique est défini sur contentView de la cellule et que les limites de contentView sont définies comme limites de la cellule lorsque la cellule est chargée (ie awakeFromNib).

Ce que vous devez savoir, c'est que votre cellule doit être plus sérieusement contrainte qu'une cellule de vue Table. Par exemple, si vous voulez que votre largeur soit dynamique, votre cellule a besoin d'une contrainte de hauteur. De même, si vous voulez que la hauteur soit dynamique, vous aurez besoin d'une contrainte de largeur pour votre cellule.

Implémentez preferredLayoutAttributesFittingAttributesdans votre cellule personnalisée

Lorsque cette fonction est appelée, votre vue a déjà été configurée avec du contenu (c'est-à-dire qu'elle cellForItema été appelée). En supposant que vos contraintes ont été correctement définies, vous pouvez avoir une implémentation comme celle-ci:

//forces the system to do one layout pass
var isHeightCalculated: Bool = false

override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
    //Exhibit A - We need to cache our calculation to prevent a crash.
    if !isHeightCalculated {
        setNeedsLayout()
        layoutIfNeeded()
        let size = contentView.systemLayoutSizeFitting(layoutAttributes.size)
        var newFrame = layoutAttributes.frame
        newFrame.size.width = CGFloat(ceilf(Float(size.width)))
        layoutAttributes.frame = newFrame
        isHeightCalculated = true
    }
    return layoutAttributes
}

REMARQUE Sur iOS 9, le comportement a changé un peu, ce qui pourrait entraîner des plantages sur votre implémentation si vous ne faites pas attention (voir plus ici ). Lorsque vous implémentez, preferredLayoutAttributesFittingAttributesvous devez vous assurer que vous ne modifiez le cadre de vos attributs de disposition qu'une seule fois. Si vous ne le faites pas, la mise en page appellera votre implémentation indéfiniment et finira par planter. Une solution consiste à mettre en cache la taille calculée dans votre cellule et à l'invalider chaque fois que vous réutilisez la cellule ou modifiez son contenu comme je l'ai fait avec la isHeightCalculatedpropriété.

Découvrez votre mise en page

À ce stade, vous devriez avoir des cellules dynamiques «fonctionnelles» dans votre collectionView. Je n'ai pas encore trouvé la solution prête à l'emploi suffisante lors de mes tests alors n'hésitez pas à commenter si vous en avez. Il se sent toujours comme UITableViewgagne la bataille pour le dimensionnement dynamique à mon humble avis.

Avertissements

N'oubliez pas que si vous utilisez des cellules prototypes pour calculer la taille estimée de l'élément, cela se cassera si votre XIB utilise des classes de taille . La raison en est que lorsque vous chargez votre cellule à partir d'un XIB, sa classe de taille sera configurée avec Undefined. Cela ne sera rompu que sur iOS 8 et versions ultérieures, car sur iOS 7, la classe de taille sera chargée en fonction de l'appareil (iPad = Regular-Any, iPhone = Compact-Any). Vous pouvez soit définir l'estimationItemSize sans charger le XIB, soit charger la cellule à partir du XIB, l'ajouter à la collectionView (cela définira le traitCollection), effectuer la mise en page, puis la supprimer de la vue d'ensemble. Alternativement, vous pouvez également faire en sorte que votre cellule remplace le traitCollectiongetter et retourne les traits appropriés. C'est à vous.

Faites-moi savoir si j'ai raté quelque chose, j'espère avoir aidé et bonne chance le codage


Daniel Galasko
la source
2
J'essaie d'implémenter la même chose avec la mise en page de flux, mais quand je la renvoie newFrame avec une taille mise à jour, la mise en page ne semble pas recalculer les positions y, qui sont toujours basées sur la hauteur de la taille estimée. Qu'est-ce qui manque?
anna
@anna appelez-vous invalidateLayout sur la mise en page?
Daniel Galasko
1
Ah, trouvé la différence, tu as fixé la hauteur estimée assez haut (400) alors que je faisais le minimum (44). Cela m'a aidé. Mais contentSize semble toujours ne pas être réglé correctement jusqu'à ce que vous fassiez défiler tout le long, donc toujours pas très utilisable. Btw, j'ai changé UITextView en UILabel, même comportement.
anna
17
Cela semble être fondamentalement cassé sur iOS 9 GM. La configuration estimatedItemSizeentraîne des plantages - il semble y avoir un énorme bogue dans la façon dont la mise en page automatique essaie de traiter l'UICollectionViewCell.
Wes Campaigne du
3
Rencontrant un problème similaire, quelqu'un a-t-il trouvé un moyen de contourner le problème?
Tony
47

Dans iOS10, il y a une nouvelle constante appelée UICollectionViewFlowLayout.automaticSize(anciennement UICollectionViewFlowLayoutAutomaticSize), donc à la place:

self.flowLayout.estimatedItemSize = CGSize(width: 100, height: 100)

vous pouvez utiliser ceci:

self.flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize

Il a de meilleures performances, en particulier lorsque les cellules de votre vue de collection ont une largeur constante

Accès à la disposition des flux:

override func viewDidLoad() {
   super.viewDidLoad()

   if let flowLayout = collectionView?.collectionViewLayout as? UICollectionViewFlowLayout {
      flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
   }
}

Swift 5 mis à jour:

override func viewDidLoad() {
   super.viewDidLoad()

   if let flowLayout = collectionView?.collectionViewLayout as? UICollectionViewFlowLayout {
      flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
    }
}
pawel221
la source
8
Où mettez-vous cela? dans viewdidload? Comment maîtrisez-vous l'écoulement?
UKDataGeek
1
J'ai réussi à l'implémenter - mais il a un comportement étrange qui le fait centrer les cellules s'il y a une seule cellule. Voici la question de débordement de pile à ce sujet - m'a perplexe: stackoverflow.com/questions/43266924/…
UKDataGeek
1
Vous pouvez accéder à la disposition du flux via la collectionViews collectionViewLayoutet vérifier simplement qu'elle est de typeUICollectionViewFlowLayout
d4Rk
4
cela rend mes cellules très petites, inutiles
user924
44

Quelques changements clés dans la réponse de Daniel Galasko ont résolu tous mes problèmes. Malheureusement, je n'ai pas assez de réputation pour commenter directement (encore).

À l'étape 1, lors de l'utilisation de la mise en page automatique, ajoutez simplement un UIView parent unique à la cellule. TOUT à l'intérieur de la cellule doit être une sous-vue du parent. Cela a répondu à tous mes problèmes. Bien que Xcode ajoute automatiquement cela pour UITableViewCells, il ne le fait pas (mais il devrait) pour UICollectionViewCells. Selon les documents :

Pour configurer l'apparence de votre cellule, ajoutez les vues nécessaires pour présenter le contenu de l'élément de données en tant que sous-vues à la vue dans la propriété contentView. N'ajoutez pas directement de sous-vues à la cellule elle-même.

Ignorez ensuite l'étape 3 entièrement. Ce n'est pas nécessaire.

Matt Koala
la source
1
Intéressant; c'est spécifiquement pour Xibs mais néanmoins merci, va essayer de brouiller avec le projet Github et voir si je peux reproduire. Peut-être diviser la réponse en Xib vs programmatique
Daniel Galasko
Très utile . Merci!
ProblemSlover
2
iOS 9, Xcode 7. Les cellules sont protoypées dans le storyboard, définissez une sous-classe personnalisée. J'ai essayé de créer une propriété appelée contentView, Xcode se plaint qu'elle entre en conflit avec une propriété existante. J'ai essayé d'ajouter des sous-vues self.contentViewet de définir des contraintes, puis l'application se bloque.
Nicolas Miari
@NicolasMiari Je ne sais pas comment faire cela par programme, ma solution est vraiment juste d'ajouter une seule uiview dans le XIB où tout se passe à l'intérieur. Vous n'avez pas besoin de créer une propriété ou quoi que ce soit.
Matt Koala
Je suis confronté au même problème que @NicolasMiari. Lorsque j'ai défini la taille estimée du contenu pour les cellules prototypées dans le storyboard avec des contraintes de mise en page automatique sur iOS 9 / Xcode 7, l'application se bloque avec une mauvaise exception d'accès et aucune trace de pile utile
wasabi
34

Dans iOS 10+, il s'agit d'un processus très simple en 2 étapes.

  1. Assurez-vous que tout le contenu de vos cellules est placé dans une seule UIView (ou dans un descendant d'UIView comme UIStackView qui simplifie beaucoup la mise en page automatique). Tout comme avec le redimensionnement dynamique des UITableViewCells, la hiérarchie de la vue entière doit avoir des contraintes configurées, du conteneur le plus externe à la vue la plus interne. Cela inclut les contraintes entre UICollectionViewCell et la vue enfant immédiate

  2. Demander à la structure de flux de votre UICollectionView de dimensionner automatiquement

    yourFlowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
Marmoy
la source
Je suis curieux, comment le fait d'envelopper le contenu de vos cellules dans une UIView améliorerait la mise en page automatique? J'aurais pensé qu'il fonctionnerait mieux avec une hiérarchie plus superficielle?
James Heald
1
Je n'ai pas parlé de performance, seulement de simplicité. Les cellules à dimensionnement automatique ne fonctionneront que si vous avez des contraintes s'étendant complètement entre la gauche et la droite, et entre le haut et le bas. Cela est plus facile à réaliser lorsque toutes les vues sont regroupées dans un seul conteneur. Si ce conteneur est un UIStackView, il est plus facile que s'il s'agit d'un autre descendant de UIView.
Marmoy
Pouvez-vous me dire comment définir une hauteur constante (comme la hauteur définie 25, et la largeur sera automatiquement modifiée) pour UICollectionViewFlowLayoutAutomaticSize.
Svetoslav Atanasov
En utilisant Swift 4.1, Xcode m'indique qu'il UICollectionViewFlowLayout.automaticSizea été renommé UICollectionViewFlowLayoutAutomaticSize.
LinusGeffarth
14
  1. Ajouter flowLayout sur viewDidLoad ()

    override func viewDidLoad() {
        super.viewDidLoad() 
        if let flowLayout = infoCollection.collectionViewLayout as? UICollectionViewFlowLayout {
            flowLayout.estimatedItemSize = CGSize(width: 1, height:1)
        }
    }
  2. Définissez également un UIView comme conteneur principal pour votre cellule et ajoutez toutes les vues requises à l'intérieur.

  3. Reportez-vous à ce tutoriel impressionnant et époustouflant pour plus de référence: UICollectionView avec cellule de redimensionnement automatique à l'aide de la mise en page automatique dans iOS 9 et 10

dianakarenms
la source
11

EDIT 19/11/19: Pour iOS 13, utilisez simplement UICollectionViewCompositionalLayout avec des hauteurs estimées. Ne perdez pas votre temps à gérer cette API cassée.

Après avoir lutté avec cela pendant un certain temps, j'ai remarqué que le redimensionnement ne fonctionne pas pour UITextViews si vous ne désactivez pas le défilement:

let textView = UITextView()
textView.scrollEnabled = false
noobular
la source
lorsque j'essaie de faire défiler, la cellule de la vue de collection n'est pas lisse. Avez-vous une solution à cela?.
Sathish Kumar Gurunathan
6

contentView ancre mystère:

Dans un cas bizarre, cela

    contentView.translatesAutoresizingMaskIntoConstraints = false

ne fonctionnerait pas. Ajout de quatre ancres explicites à contentView et cela a fonctionné.

class AnnoyingCell: UICollectionViewCell {
    
    @IBOutlet var word: UILabel!
    
    override init(frame: CGRect) {
        super.init(frame: frame); common() }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder); common() }
    
    private func common() {
        contentView.translatesAutoresizingMaskIntoConstraints = false
        
        NSLayoutConstraint.activate([
            contentView.leftAnchor.constraint(equalTo: leftAnchor),
            contentView.rightAnchor.constraint(equalTo: rightAnchor),
            contentView.topAnchor.constraint(equalTo: topAnchor),
            contentView.bottomAnchor.constraint(equalTo: bottomAnchor)
        ])
    }
}

et comme d'habitude

    estimatedItemSize = UICollectionViewFlowLayout.automaticSize

dans YourLayout: UICollectionViewFlowLayout

Qui sait? Pourrait aider quelqu'un.

Crédit

https://www.vadimbulavin.com/collection-view-cells-self-sizing/

trébuché sur la pointe là-bas - je ne l'ai jamais vu nulle part ailleurs dans tous les articles des 1000 sur ce sujet.

Fattie
la source
3

J'ai fait une hauteur de cellule dynamique de la vue de la collection. Voici le dépôt git hub .

Et, découvrez pourquoi preferLayoutAttributesFittingAttributes est appelé plusieurs fois. En fait, il sera appelé au moins 3 fois.

L'image du journal de la console: entrez la description de l'image ici

1er PreferredLayoutAttributesFittingAttributes :

(lldb) po layoutAttributes
<UICollectionViewLayoutAttributes: 0x7fa405c290e0> index path: (<NSIndexPath:    0xc000000000000016> 
{length = 2, path = 0 - 0}); frame = (15 12; 384 57.5); 

(lldb) po self.collectionView
<UICollectionView: 0x7fa40606c800; frame = (0 57.6667; 384 0);

Le layoutAttributes.frame.size.height est le statut actuel 57.5 .

2ème PreferredLayoutAttributesFittingAttributes :

(lldb) po layoutAttributes
<UICollectionViewLayoutAttributes: 0x7fa405c16370> index path: (<NSIndexPath: 0xc000000000000016> 
{length = 2, path = 0 - 0}); frame = (15 12; 384 534.5); 

(lldb) po self.collectionView
<UICollectionView: 0x7fa40606c800; frame = (0 57.6667; 384 0);

La hauteur du cadre cellulaire est passée à 534,5 comme prévu. Mais, la vue de la collection reste à hauteur nulle.

3e PreferredLayoutAttributesFittingAttributes :

(lldb) po layoutAttributes
<UICollectionViewLayoutAttributes: 0x7fa403d516a0> index path: (<NSIndexPath: 0xc000000000000016> 
{length = 2, path = 0 - 0}); frame = (15 12; 384 534.5); 

(lldb) po self.collectionView
<UICollectionView: 0x7fa40606c800; frame = (0 57.6667; 384 477);

Vous pouvez voir que la hauteur de la vue de la collection est passée de 0 à 477 .

Le comportement est similaire pour gérer le défilement:

1. Before self-sizing cell

2. Validated self-sizing cell again after other cells recalculated.

3. Did changed self-sizing cell

Au début, je pensais que cette méthode n'appelait qu'une seule fois. J'ai donc codé comme suit:

CGRect frame = layoutAttributes.frame;
frame.size.height = frame.size.height + self.collectionView.contentSize.height;
UICollectionViewLayoutAttributes* newAttributes = [layoutAttributes copy];
newAttributes.frame = frame;
return newAttributes;

Cette ligne:

frame.size.height = frame.size.height + self.collectionView.contentSize.height;

entraînera la boucle infinie des appels système et le blocage de l'application.

Toute taille modifiée, il validera encore et encore toutes les cellules préférées de LayoutAttributesFittingAttributes jusqu'à ce que les positions de chaque cellule (c'est-à-dire les cadres) ne changent plus.

Yang Young
la source
2

En plus des réponses ci-dessus,

Il suffit de vous rendre vous définissez estimatedItemSize propriété de UICollectionViewFlowLayout une certaine taille et ne pas mettre en œuvre sizeForItem: atIndexPath méthode déléguée.

C'est tout.

Murat Yasar
la source
1

Si vous implémentez la méthode UICollectionViewDelegateFlowLayout:

- (CGSize)collectionView:(UICollectionView*)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath*)indexPath

Lorsque vous appelez collectionview performBatchUpdates:completion:, la taille de taille utilisera sizeForItemAtIndexPathau lieu de preferredLayoutAttributesFittingAttributes.

Le processus de rendu performBatchUpdates:completionpassera par la méthode preferredLayoutAttributesFittingAttributesmais il ignore vos modifications.

Yang Young
la source
J'ai vu ce mauvais comportement, mais seulement lorsque la taille estimée est nulle. Êtes-vous sûr de définir une taille estimée?
dgatwood
1

À qui cela peut aider,

J'ai eu ce vilain crash si estimatedItemSizec'était réglé. Même si je suis retourné 0 po numberOfItemsInSection. Par conséquent, les cellules elles-mêmes et leur mise en page automatique n'étaient pas à l'origine du crash ... La collectionView vient de planter, même lorsqu'elle est vide, simplement parce qu'elle a estimatedItemSizeété définie pour l'auto-dimensionnement.

Dans mon cas, j'ai réorganisé mon projet, d'un contrôleur contenant une collectionView à un collectionViewController, et cela a fonctionné.

Allez comprendre.

Jean Le Moignan
la source
1
Essayez d'appeler collectionView.collectionViewLayout.invalidateLayout()après collectionView.reloadData().
chengsam
pour ios10 et ci-dessous, ajoutez ce code `override func viewWillLayoutSubviews () {super.viewWillLayoutSubviews () if #available (iOS 11.0, *) {} else {mainCollectionView.collectionViewLayout.invalidateLayout ()}}`
Marosdee Uma
1

Pour tous ceux qui ont tout essayé sans chance, c'est la seule chose qui l'a fait fonctionner pour moi. Pour les étiquettes multilignes à l'intérieur de la cellule, essayez d'ajouter cette ligne magique:

label.preferredMaxLayoutWidth = 200

Plus d'infos: ici

À votre santé!

HelloimDarius
la source
0

L'exemple de méthode ci-dessus ne se compile pas. Voici une version corrigée (mais non testée pour savoir si cela fonctionne ou non.)

override func preferredLayoutAttributesFittingAttributes(layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes 
{
    let attr: UICollectionViewLayoutAttributes = layoutAttributes.copy() as! UICollectionViewLayoutAttributes

    var newFrame = attr.frame
    self.frame = newFrame

    self.setNeedsLayout()
    self.layoutIfNeeded()

    let desiredHeight: CGFloat = self.contentView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height
    newFrame.size.height = desiredHeight
    attr.frame = newFrame
    return attr
}
user3246173
la source
0

Mettre à jour plus d'informations:

  • Si vous utilisez flowLayout.estimatedItemSize, suggérez d'utiliser la version ultérieure d'iOS8.3. Avant iOS8.3, il plantera [super layoutAttributesForElementsInRect:rect];. Le message d'erreur est

    *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[__NSArrayM insertObject:atIndex:]: object cannot be nil'

  • Deuxièmement, dans la version iOS8.x, un flowLayout.estimatedItemSizeparamètre d'encart de section différent ne fonctionnera pas. fonction -à- dire: (UIEdgeInsets)collectionView:layout:insetForSectionAtIndex:.

Yang Young
la source
0

La solution comprend 4 étapes importantes:

  1. Activation du dimensionnement dynamique des cellules

flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize

  1. Activez explicitement la disposition automatique et l'épingle contentViewsur les bords de la cellule car elle contentViewempêche le dimensionnement automatique, étant donné que la disposition automatique n'est pas activée.
class MultiLineCell: UICollectionViewCell{

    private var textViewHeightContraint: NSLayoutConstraint!

    override init(frame: CGRect) {
        super.init(frame: frame)
        backgroundColor = .cyan
        contentView.translatesAutoresizingMaskIntoConstraints = false
        contentViewWidthAnchor = contentView.widthAnchor.constraint(equalToConstant: 0)

        NSLayoutConstraint.activate([
            contentView.leadingAnchor.constraint(equalTo: leadingAnchor),
            contentView.topAnchor.constraint(equalTo: topAnchor),
            contentView.trailingAnchor.constraint(equalTo: trailingAnchor),
            contentView.bottomAnchor.constraint(equalTo: bottomAnchor),
        ])
    } 
    ...
}
  1. Définissez contentView.widthAnchor.constraint de collectionView(:cellForItemAt:)pour limiter la largeur de contentView à la largeur de collectionView.

Cela se passe comme suit; nous allons définir la maxWidthvariable de la cellule sur la largeur de CollectionView selon la méthode de collectionView(:cellForItemAt:)et dans maxWidth, didSetnous allons définir la largeurAnchor.constant sur maxWidth.

class ViewController: UIViewController, UICollectionViewDataSource {
    ...

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellId", for: indexPath) as! MultiLineCell
        cell.textView.text = dummyTextMessages[indexPath.row]
        cell.maxWidth = collectionView.frame.width
        return cell
    }

    ...
}
class MultiLineCell: UICollectionViewCell{
    ....

    var maxWidth: CGFloat? {
        didSet {
            guard let maxWidth = maxWidth else {
                return
            }
            contentViewWidthAnchor.constant = maxWidth
            contentViewWidthAnchor.isActive = true
        }
    }

    ....
}

Puisque vous souhaitez activer l'auto-dimensionnement de UITextView, il a une étape supplémentaire pour;

4. Calculez et définissez la hauteurAnchor.constant de UITextView.

Ainsi, chaque fois que la largeur de contentView est définie, nous ajustons la hauteur de UITextView le long didSetde maxWidth.

Dans UICollectionViewCell:

var maxWidth: CGFloat? {
    didSet {
        guard let maxWidth = maxWidth else {
            return
        }
        contentViewWidthAnchor.constant = maxWidth
        contentViewWidthAnchor.isActive = true

        let sizeToFitIn = CGSize(width: maxWidth, height: CGFloat(MAXFLOAT))
        let newSize = self.textView.sizeThatFits(sizeToFitIn)
        self.textViewHeightContraint.constant = newSize.height
    }
}

Ces étapes vous permettront d'obtenir le résultat souhaité. Voici un runnable complet essentiel

Merci à Vadim Bulavin pour son article de blog clair et instructif - Collection View Cells Self-Sizing: Step by Step Tutorial

Harley
la source
-2

J'ai essayé d'utiliser, estimatedItemSizemais il y avait un tas de bugs lors de l'insertion et de la suppression de cellules si elles estimatedItemSizen'étaient pas exactement égales à la hauteur de la cellule. J'ai arrêté de définir estimatedItemSizeet mis en œuvre des cellules dynamiques en utilisant une cellule prototype. voici comment cela se fait:

créer ce protocole:

protocol SizeableCollectionViewCell {
    func fittedSize(forConstrainedSize size: CGSize)->CGSize
}

implémentez ce protocole à votre façon UICollectionViewCell:

class YourCustomCollectionViewCell: UICollectionViewCell, SizeableCollectionViewCell {

    @IBOutlet private var mTitle: UILabel!
    @IBOutlet private var mDescription: UILabel!
    @IBOutlet private var mContentView: UIView!
    @IBOutlet private var mTitleTopConstraint: NSLayoutConstraint!
    @IBOutlet private var mDesciptionBottomConstraint: NSLayoutConstraint!

    func fittedSize(forConstrainedSize size: CGSize)->CGSize {

        let fittedSize: CGSize!

        //if height is greatest value, then it's dynamic, so it must be calculated
        if size.height == CGFLoat.greatestFiniteMagnitude {

            var height: CGFloat = 0

            /*now here's where you want to add all the heights up of your views.
              apple provides a method called sizeThatFits(size:), but it's not 
              implemented by default; except for some concrete subclasses such 
              as UILabel, UIButton, etc. search to see if the classes you use implement 
              it. here's how it would be used:
            */
            height += mTitle.sizeThatFits(size).height
            height += mDescription.sizeThatFits(size).height
            height += mCustomView.sizeThatFits(size).height    //you'll have to implement this in your custom view

            //anything that takes up height in the cell has to be included, including top/bottom margin constraints
            height += mTitleTopConstraint.constant
            height += mDescriptionBottomConstraint.constant

            fittedSize = CGSize(width: size.width, height: height)
        }
        //else width is greatest value, if not, you did something wrong
        else {
            //do the same thing that's done for height but with width, remember to include leading/trailing margins in calculations
        }

        return fittedSize
    }
}

maintenant, faites en sorte que votre contrôleur se conforme à UICollectionViewDelegateFlowLayoutce champ:

class YourViewController: UIViewController, UICollectionViewDelegateFlowLayout {
    private var mCustomCellPrototype = UINib(nibName: <name of the nib file for your custom collectionviewcell>, bundle: nil).instantiate(withOwner: nil, options: nil).first as! SizeableCollectionViewCell
}

il sera utilisé comme cellule prototype pour lier des données à, puis déterminer comment ces données ont affecté la dimension que vous souhaitez dynamiser

enfin, le UICollectionViewDelegateFlowLayout's collectionView(:layout:sizeForItemAt:)doit être implémenté:

class YourViewController: UIViewController, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource {

    private var mDataSource: [CustomModel]

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath)->CGSize {

        //bind the prototype cell with the data that corresponds to this index path
        mCustomCellPrototype.bind(model: mDataSource[indexPath.row])    //this is the same method you would use to reconfigure the cells that you dequeue in collectionView(:cellForItemAt:). i'm calling it bind

        //define the dimension you want constrained
        let width = UIScreen.main.bounds.size.width - 20    //the width you want your cells to be
        let height = CGFloat.greatestFiniteMagnitude    //height has the greatest finite magnitude, so in this code, that means it will be dynamic
        let constrainedSize = CGSize(width: width, height: height)

        //determine the size the cell will be given this data and return it
        return mCustomCellPrototype.fittedSize(forConstrainedSize: constrainedSize)
    }
}

et c'est tout. Renvoyer la taille de la cellule de collectionView(:layout:sizeForItemAt:)cette manière m'empêche d'avoir à utiliser estimatedItemSize, et l'insertion et la suppression de cellules fonctionne parfaitement.

chaussure
la source