web-dev-qa-db-fra.com

bug iOS 10: UICollectionView a reçu les attributs de mise en forme pour une cellule avec un chemin d'index inexistant

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?

60
cmacera

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];
118
Katrin Annuk

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()
    }
35
ArturC

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.

15
Aximem

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];
4
Andrey

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()
3
Kevin Bel

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
}
3
Nick Kallen

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.

3
m8labs

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

2
jeff ayan

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
    }
    ...
1
Mol0ko

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)
1
Mati Bot

Réinitialiser la disposition de UICollectionView a résolu le problème pour moi:

let layout = UICollectionViewFlowLayout()
collectionView?.collectionViewLayout = layout
1
krlb

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

0
Gulfam Khan

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.

0
Javier Alzueta

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 UICollectionViews: un pour Foos et un pour Bars. 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.

0
Heath Borders

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

0
Trev14

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()
        }
    })
}
0
dimazava

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.

0
Arshad Shaik

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.

0
ullstrm