Exécution de mon application sur un appareil avec iOS 10, le message d'erreur suivant s'affiche:
UICollectionView a reçu les attributs de mise en page pour une cellule dont le chemin d'index n'existe pas
Dans iOS 8 et 9 fonctionne bien. J'ai fait des recherches et j'ai découvert qu'il y avait un problème d'invalidation de la présentation de la collection. J'ai essayé d'implémenter cette solution sans succès, alors j'aimerais demander de l'aide directe. Voici ma vue hiérarchique:
->Table view
->Each cell of table is a custom collection view [GitHub Repo][1]
->Each item of collection view has another collection view
Ce que j'ai essayé est d'insérer
[self.collectionView.collectionViewLayout invalidateLayout];
Dans le
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
des deux vues de collection.
De plus, j'ai essayé d'invalider la mise en page avant de recharger des données, cela ne fonctionne pas ...
Quelqu'un pourrait-il me donner des instructions à prendre?
Cela m'est arrivé lorsque le nombre de cellules dans collectionView a changé. Il s'avère qu'il me manquait invalidateLayout après avoir appelé reloadData. Après l'avoir ajouté, je n'ai plus eu de crash. Apple a apporté quelques modifications à collectionViews dans iOS10. Je suppose que c'est la raison pour laquelle nous ne rencontrons pas le même problème sur les versions antérieures.
Voici mon code final:
[self.collectionView reloadData];
[self.collectionView.collectionViewLayout invalidateLayout];
Lorsque vous avez une disposition personnalisée, n'oubliez pas de vider le cache (UICollectionViewLayoutAttributes) tout en ignorant prepare func.
override func prepare() {
super.prepare()
cache.removeAll()
}
J'ai également rencontré ce problème avec 2 collectionViews dans la même vue car j'ai ajouté le même UICollectionViewFlowLayout dans les deux collections:
let layout = UICollectionViewFlowLayout()
collectionView1.collectionViewLayout = layout
collectionView2.collectionViewLayout = layout
Bien sûr, il se bloque lors du rechargement de données si collectionView1 Data change. Si cela pouvait aider quelqu'un.
La réponse précédente est utile, mais si vous utilisez des cellules à sélection automatique, leur taille sera incorrecte.
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
layout.estimatedItemSize = CGSizeMake(60, 25);
layout.itemSize = UICollectionViewFlowLayoutAutomaticSize;
self.collectionView.collectionViewLayout = layout;
Je résous ces problèmes en remplaçant
[self.collectionView reloadData];
à
[self.collectionView reloadSections:indexSet];
Ce n'est pas la meilleure chose à reloadData à chaque fois (vous devez utiliser insertItems et deleteItems, et même reloadSections). Mais ... après avoir dit que dans certains cas, c'est valide, vous pouvez le faire:
collectionView.dataSource = nil
collectionView.delegate = nil
/*... All changes here. ... */
collectionView.dataSource = self
collectionView.delegate = self
collectionView.reloadData()
L'appel de invalidateLayout
n'a pas empêché le crash dans mon cas. (Cela fonctionnait si le nombre d'éléments dans la vue de la collection augmentait mais pas s'il diminuait). Contexte: j'ai un UICollectionView dans un UITableViewCell et lorsque la cellule de la table est réutilisée, je réinitialise les délégués de la collectionView. J'ai résolu le problème non pas en invalidant le cache, mais en RECREAISSANT l'objet de présentation chaque fois que je réinitialisais le délégué, puis en appelant reloadData ():
foo.dataSource = newDataSource
foo.delegate = newDelegate
foo.fixLayoutBug()
foo.reloadData()
func fixLayoutBug() {
let oldLayout = collectionViewLayout as! UICollectionViewFlowLayout
let newLayout = UICollectionViewFlowLayout()
newLayout.estimatedItemSize = oldLayout.estimatedItemSize
newLayout.footerReferenceSize = oldLayout.footerReferenceSize
newLayout.headerReferenceSize = oldLayout.headerReferenceSize
newLayout.itemSize = oldLayout.itemSize
newLayout.minimumInteritemSpacing = oldLayout.minimumInteritemSpacing
newLayout.minimumLineSpacing = oldLayout.minimumLineSpacing
newLayout.scrollDirection = oldLayout.scrollDirection
newLayout.sectionFootersPinToVisibleBounds = oldLayout.sectionFootersPinToVisibleBounds
newLayout.sectionHeadersPinToVisibleBounds = oldLayout.sectionHeadersPinToVisibleBounds
newLayout.sectionInset = oldLayout.sectionInset
newLayout.sectionInsetReference = oldLayout.sectionInsetReference
collectionViewLayout = newLayout
}
La réponse de @ Katrin a beaucoup aidé, mais je pouvais obtenir des résultats encore meilleurs en ajoutant une ligne supplémentaire:
collectionView.reloadData()
collectionView.collectionViewLayout.invalidateLayout()
collectionView.layoutSubviews() // <-- here it is :)
Je ne peux pas dire maintenant si je pouvais reproduire le plantage avec cette ligne ou non, mais je suppose qu'il y en avait une ... Alors, toujours pas une solution miracle, mais quelque chose.
Dans mon cas, je changeais la constante NSlayoutConstraint
self.collectionViewContacts.collectionViewLayout.invalidateLayout()
UIView.animate(withDuration: 0.3) {
self.view.layoutIfNeeded()
}
self.collectionViewContacts.reloadData()
résolu le problème
J'ai eu le problème en utilisant RxDataSources avec RxCollectionViewSectionedAnimatedDataSource
et je l'ai résolu en combinant deux réponses. Je dois invalidateLayout()
quand recharger ma collection de manière réactive:
...
.asDriver(onErrorJustReturn: [])
.do(onNext: { [weak self] _ in
DispatchQueue.main.async {
self?.collectionViewLayout.invalidateLayout()
self?.collectionView.layoutSubviews()
}
}).drive(collectionView.rx.items(dataSource: collectionController.dataSource))
.disposed(by: disposeBag)
et effacez cache
tableau (s) dans la méthode de mise en forme prepare()
. Voici le code:
override func prepare() {
super.prepare()
cache.removeAll()
guard let collectionView = collectionView,
collectionView.numberOfSections > 0,
let delegate = delegate else {
return
}
...
Si vous souhaitez conserver votre position de défilement et résoudre le problème, vous pouvez utiliser ceci:
collectionView.reloadData()
let context = collectionView.collectionViewLayout.invalidationContext(forBoundsChange: collectionView.bounds)
context.contentOffsetAdjustment = CGPoint.zero
collectionView.collectionViewLayout.invalidateLayout(with: context)
Réinitialiser la disposition de UICollectionView
a résolu le problème pour moi:
let layout = UICollectionViewFlowLayout()
collectionView?.collectionViewLayout = layout
Dans mon cas, je changeais la constante de NSLayoutConstraint, aucune des solutions ci-dessus ne fonctionnait pour moi. grâce à @ jeff ayan, dont la réponse m'a permis de comprendre. J'ai résolu le problème avec ce code
override func prepareForReuse() {
super.prepareForReuse()
imagesCollectionHeight.constant = 100 // changing collectionview height constant
imageContainerWidth.constant = 100 //changing width constant of some view
self.layoutIfNeeded() // this line did the trick for me
}
J'espère que ça aide quelqu'un
Ce qui suit l'a fait pour moi:
[self.collectionView reloadData];
[self.collectionView layoutIfNeeded];
Ce code semble forcer collectionView à accéder à la première cellule avant de recharger des données, ce qui semble provoquer l'erreur dans mon cas.
Cela m'est aussi arrivé, mais c'est parce que ma UICollectionViewDataSource
a changé et que je n'ai pas appelé -[UICollectionView reloadData]
. Dans mon cas, j'avais la structure de données suivante:
struct Bar { let name: String }
struct Foo { let name: String; let bars: [Bar] }
J'avais deux UICollectionView
s: un pour Foo
s et un pour Bar
s. Dans mon -collectionView:didSelectItemAtIndexPath:
, j'avais les éléments suivants:
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
if (collectionView == self.fooCollectionView) {
self.selectedFoo = self.foos[indexPath.row];
[self.barCollectionView reloadData];
self.fooCollectionView.hidden = YES;
self.barCollectionView.hidden = NO;
} else if (collectionView == self.barCollectionView) {
// do some stuff with the selected bar
self.selectedFoo = nil;
// This -reloadData call fixed my error. I thought I didn't
// need it since my UICollectionView was hidden
[self.barCollectionView reloadData];
self.barCollectionView.hidden = YES;
self.fooCollectionView.hidden = NO;
}
}
Sans l'appel -reloadData
, je verrais le crash lorsque je ferais pivoter l'appareil.
J'ai eu ce problème aussi. Dans mon cas, il y avait une UICollectionViewLayout
personnalisée appliquée à la vue de collection et le problème était qu'elle contenait des données obsolètes pour le nombre de cellules à afficher. C'est certainement quelque chose que vous pouvez vérifier quand vous voyez UICollectionView received layout attributes for a cell with an index path that does not exist
J'ai rencontré le même problème lorsque la vue collection-masquage utilisant des contraintes animées a changé. Ma solution est basée sur les réponses existantes.
self.filtersCollectionView.reloadData()
if isNeedToUpdateConstraint {
filtersCollectionViewHeightLayoutConstraint.constant = updatedHeight
UIView.animate(withDuration: 0.1, animations: {
self.view.setNeedsLayout()
}, completion: { completion in
if completion {
self.filtersCollectionView.collectionViewLayout.invalidateLayout()
}
})
}
Après avoir passé deux jours de temps, le code ci-dessous a résolu le problème. Avant de charger une collectionview, écrivez le code ci-dessous.
let flowLayout = UICollectionViewFlowLayout()
flowLayout.scrollDirection = .horizontal
collecView.collectionViewLayout = flowLayout
collecView.delegate = self
collecView.dataSource = self
collecView.reloadData()
Ensuite, le crash sera résolu, mais vous rencontrerez un problème dans les cellules collectionview, que les cellules soient compressées ou non selon votre conception. Puis juste une ligne de code après scrolldirection line
flowLayout.itemSize = CGSize(width: 92, height: 100)
Avec la ligne de code ci-dessus, vous pouvez ajuster votre disposition de cellule collectionview.
J'espère que ça va aider quelqu'un.
J'ai réussi à résoudre ce problème en recréant la collectionViewLayout
avant d'appeler reloadData()
Le problème était probablement lié au fait que j’avais une instance distincte pour la variable dataSource
(c’est-à-dire pas le contrôleur de vue qui détient la collectionView). Lors de la permutation de la source de données, le blocage pouvait apparaître.