J'essaie d'obtenir l'auto-dimensionnement UICollectionViewCells
en utilisant la mise en page automatique, mais je n'arrive pas à faire en sorte que les cellules se rediment elles-mêmes au contenu. Je ne parviens pas à comprendre comment la taille de la cellule est mise à jour à partir du contenu de son contenu dans le contenu de la cellule.
Voici la configuration que j'ai essayée:
UICollectionViewCell
avec un UITextView
dans son contentView.UITextView
est désactivé.UITextView
est épinglée à gauche de la cellule avec une largeur explicite de 320.UITextView
épinglé en haut de la cellule.UITextView
est définie sur une constante à laquelle les rapports UITextView
s'ajustent au texte.Voici à quoi cela ressemble avec l'arrière-plan de la cellule défini sur rouge et l'arrière-plan UITextView
sur bleu:
Je mets le projet que j'ai joué avec GitHub ici .
systemLayoutSizeFittingSize
renommé en systemLayoutSizeFitting
Après avoir vu ma solution GitHub casser sous iOS 9, j'ai enfin eu le temps d'enquêter sur le problème. J'ai maintenant mis à jour le référentiel afin d'inclure plusieurs exemples de configurations différentes pour l'auto-dimensionnement des cellules. Ma conclusion est que les cellules auto-dimensionnantes sont géniales en théorie mais en désordre en pratique. Un mot de prudence lorsque vous procédez à l'auto-dimensionnement des cellules.
TL; DR
Découvrez mon projet GitHub
Les cellules auto-dimensionnées sont uniquement prises en charge avec la disposition de flux, assurez-vous que c'est bien ce que vous utilisez.
Vous devez configurer deux éléments pour que les cellules de dimensionnement automatique fonctionnent.
estimatedItemSize
sur UICollectionViewFlowLayout
La structure de flux deviendra de nature dynamique une fois que vous aurez défini la propriété estimatedItemSize
.
self.flowLayout.estimatedItemSize = CGSize(width: 100, height: 100)
Cela vient en 2 saveurs; Mise en page automatique ou remplacement personnalisé de preferredLayoutAttributesFittingAttributes
.
Je n'entrerai pas dans les détails car il y a un brillant SO post à propos de 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 telles que vous assurer que le paramètre autoresizingMask est défini sur le contenu de la cellule et que ses limites sont définies. est défini comme les limites de la cellule lorsque la cellule est chargée (c'est-à-dire awakeFromNib
).
Vous devez savoir que votre cellule doit être plus sérieusement contrainte qu'une cellule en mode tableau. Par exemple, si vous souhaitez que votre largeur soit dynamique, votre cellule a besoin d'une contrainte de hauteur. De même, si vous souhaitez que la hauteur soit dynamique, vous aurez besoin d'une contrainte de largeur pour votre cellule.
preferredLayoutAttributesFittingAttributes
dans votre cellule personnaliséeLorsque cette fonction est appelée, votre vue a déjà été configurée avec un contenu (c'est-à-dire cellForItem
a été appelé). En supposant que vos contraintes soient correctement définies, vous pourriez avoir une implémentation comme celle-ci:
//forces the system to do one layout pass
var isHeightCalculated: Bool = false
override func preferredLayoutAttributesFittingAttributes(_ 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
}
NOTE Sur iOS 9, le comportement a changé un peu, ce qui pourrait provoquer des plantages sur votre implémentation si vous ne faites pas attention (Voir plus ici ). Lorsque vous implémentez preferredLayoutAttributesFittingAttributes
, vous devez vous assurer de ne modifier qu'une seule fois le cadre de vos attributs de présentation. Si vous ne le faites pas, la mise en page appellera votre mise en œuvre 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 propriété isHeightCalculated
.
À ce stade, vous devriez avoir des cellules dynamiques 'fonctionnant' 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. On a toujours l’impression que UITableView
gagne la bataille du dimensionnement dynamique à mon humble avis.
Soyez très conscient que si vous utilisez des cellules prototypes pour calculer l’estimation de la taille estimée - ceci se brisera si votre XIB utilise des classes de taille . La raison en est que lorsque vous chargez votre cellule à partir d'un fichier XIB, sa classe de taille est configurée avec Undefined
. Cela ne sera interrompu que sur iOS 8 et plus, car sur iOS 7, la classe de taille sera chargée en fonction du périphérique (iPad = Normal-Any, iPhone = Compact-Any). Vous pouvez soit définir la valeur EstimatedItemSize sans charger le fichier XIB, soit charger la cellule à partir du fichier XIB, l'ajouter à la collectionView (pour définir le traitCollection), effectuer la mise en page, puis la supprimer de la vue d'ensemble. Sinon, vous pouvez également demander à votre cellule de remplacer le getter traitCollection
et de renvoyer les traits appropriés. C'est à vous.
Dites-moi si j'ai oublié quelque chose, j'espère que j'ai aidé et bonne chance pour le codage
Quelques changements clés à réponse de Daniel Galasko ont résolu tous mes problèmes. Malheureusement, je n'ai pas assez de réputation pour commenter directement (pas encore).
À l'étape 1, lorsque vous utilisez la disposition automatique, ajoutez simplement un UIView parent unique à la cellule. TOUT dans la cellule doit être une sous-vue du parent. Cela a répondu à tous mes problèmes. Bien que Xcode ajoute cela automatiquement pour UITableViewCells, ce n'est pas le cas (mais cela devrait être le cas) pour UICollectionViewCells. Selon le docs :
Pour configurer l’apparence de votre cellule, ajoutez les vues nécessaires à la présentation du contenu de l’élément de données sous forme de sous-vues à la vue de la propriété contentView. Ne pas ajouter directement des sous-vues à la cellule elle-même.
Ensuite, ignorez entièrement l'étape 3. Ce n'est pas nécessaire.
Dans iOS10, il existe 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
Ses performances sont meilleures, en particulier lorsque les cellules de votre vue Collection ont une largeur constante.
Accès à la disposition du flux:
override func viewDidLoad() {
super.viewDidLoad()
if let flowLayout = collectionView?.collectionViewLayout as? UICollectionViewFlowLayout {
flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
}
}
Dans iOS 10+, il s'agit d'un processus très simple en 2 étapes.
Assurez-vous que tout le contenu de votre cellule est placé dans une seule UIView (ou dans un descendant de UIView comme UIStackView, ce qui simplifie beaucoup la mise en mémoire automatique). Tout comme avec le redimensionnement dynamique de UITableViewCells, toute la hiérarchie de vues doit avoir des contraintes configurées, du conteneur le plus externe à la vue la plus à l'intérieur. Cela inclut les contraintes entre UICollectionViewCell et la vue enfant immédiate
Indiquez à Flowlayout de votre UICollectionView de le redimensionner automatiquement
yourFlowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
Ajouter flowLayout sur viewDidLoad ()
override func viewDidLoad() {
super.viewDidLoad()
if let flowLayout = infoCollection.collectionViewLayout as? UICollectionViewFlowLayout {
flowLayout.estimatedItemSize = CGSize(width: 1, height:1)
}
}
Définissez également un UIView comme conteneur principal pour votre cellule et ajoutez toutes les vues requises à l'intérieur de celle-ci.
Reportez-vous à ce didacticiel génial et époustouflant pour une référence ultérieure: ICollectionView avec cellule à taille automatique à l'aide de la mise en pause automatique sous iOS 9 & 1
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
J'ai créé une hauteur de cellule dynamique dans la vue de collection. Voici repo hub git .
Et, expliquez pourquoi preferredLayoutAttributesFittingAttributes est appelée plus d'une fois. En fait, il sera appelé au moins 3 fois.
L'image du journal de la console:
1st 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);
LayoutAttributes.frame.size.height est le statut actuel 57.5 .
2nd 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 de la cellule a été modifiée en 534.5 comme prévu. Mais, la vue de la collection reste à zéro.
3rd 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 collection est passée de 0 à 477 .
Le comportement est similaire à 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 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;
provoquera une boucle infinie de l'appel système et un blocage de l'application.
Toute taille modifiée, elle validera encore et encore les couches preferredLayoutAttributesFittingAttributes de toutes les cellules jusqu'à ce que les positions de chaque cellule (c'est-à-dire les cadres) ne soient plus modifiées.
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 hauteur utilisera sizeForItemAtIndexPath
au lieu de preferredLayoutAttributesFittingAttributes
.
Le processus de rendu de performBatchUpdates:completion
suivra la méthode preferredLayoutAttributesFittingAttributes
mais il ignorera vos modifications.
À qui que ce soit, cela peut aider,
J'ai eu ce crash si estimatedItemSize
était réglé. Même si je suis retourné 0 dans numberOfItemsInSection
. Par conséquent, les cellules elles-mêmes et leur mise en page automatique n'étaient pas la cause du crash ... La collectionView venait de tomber en panne, même lorsqu'elle était vide, simplement parce que estimatedItemSize
était défini pour un auto-dimensionnement.
Dans mon cas, j'ai réorganisé mon projet, d'un contrôleur contenant une collectionView à une collectionViewController, et cela a fonctionné.
Allez comprendre.
L'exemple de méthode ci-dessus ne 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
}
Mettre à jour plus d'informations:
Si vous utilisez flowLayout.estimatedItemSize
, suggérez d'utiliser iOS8.3 version ultérieure. Avant iOS8.3, il se planterait [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, flowLayout.estimatedItemSize
entraînera un réglage différent de l'insert de section. c'est-à-dire fonction: (UIEdgeInsets)collectionView:layout:insetForSectionAtIndex:
.
En plus des réponses ci-dessus,
Assurez-vous simplement que vous définissez installedItemSize propriété de UICollectionViewFlowLayout à une taille et à - ne pas implémenter sizeForItem: atIndexPath méthode déléguée.
C'est ça.