Je reçois cette erreur lors de l'exécution de insertItemsAtIndexPaths
dans UICollectionView
Échec de l'assertion dans:
-[UICollectionViewData indexPathForItemAtGlobalIndex:],
/SourceCache/UIKit/UIKit-2372/UICollectionViewData.m:442
2012-09-26 18:12:34.432
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException',
reason: 'request for index path for global index 805306367
when there are only 1 items in the collection view'
J'ai vérifié et ma source de données ne contient qu'un seul élément. Avez-vous une idée de pourquoi cela pourrait arriver? Si plus d'informations sont nécessaires, je peux certainement vous les fournir.
J'ai rencontré ce même problème lors de l'insertion de la première cellule dans une vue de collection. J'ai résolu le problème en modifiant mon code afin d'appeler l'UICollectionView
- (void)reloadData
lors de l'insertion de la première cellule, mais
- (void)insertItemsAtIndexPaths:(NSArray *)indexPaths
lors de l'insertion de toutes les autres cellules.
Fait intéressant, j'ai aussi eu un problème avec
- (void)deleteItemsAtIndexPaths:(NSArray *)indexPaths
lors de la suppression de la dernière cellule. J'ai fait la même chose que précédemment: il suffit d'appeler reloadData
lors de la suppression de la dernière cellule.
L'insertion de la section # 0 juste avant l'insertion des cellules semble rendre UICollectionView heureuse.
NSArray *indexPaths = /* indexPaths of the cells to be inserted */
NSUInteger countBeforeInsert = _cells.count;
dispatch_block_t updates = ^{
if (countBeforeInsert < 1) {
[self.collectionView insertSections:[NSIndexSet indexSetWithIndex:0]];
}
[self.collectionView insertItemsAtIndexPaths:indexPaths];
};
[self.collectionView performBatchUpdates:updates completion:nil];
J'ai posté une solution pour ce problème ici: https://Gist.github.com/iwasrobbed/5528897
Dans la catégorie privée en haut de votre .m
fichier:
@interface MyViewController ()
{
BOOL shouldReloadCollectionView;
NSBlockOperation *blockOperation;
}
@end
Les rappels de vos délégués seraient alors:
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
{
shouldReloadCollectionView = NO;
blockOperation = [NSBlockOperation new];
}
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id<NSFetchedResultsSectionInfo>)sectionInfo
atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type
{
__weak UICollectionView *collectionView = self.collectionView;
switch (type) {
case NSFetchedResultsChangeInsert: {
[blockOperation addExecutionBlock:^{
[collectionView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex]];
}];
break;
}
case NSFetchedResultsChangeDelete: {
[blockOperation addExecutionBlock:^{
[collectionView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex]];
}];
break;
}
case NSFetchedResultsChangeUpdate: {
[blockOperation addExecutionBlock:^{
[collectionView reloadSections:[NSIndexSet indexSetWithIndex:sectionIndex]];
}];
break;
}
default:
break;
}
}
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath
{
__weak UICollectionView *collectionView = self.collectionView;
switch (type) {
case NSFetchedResultsChangeInsert: {
if ([self.collectionView numberOfSections] > 0) {
if ([self.collectionView numberOfItemsInSection:indexPath.section] == 0) {
shouldReloadCollectionView = YES;
} else {
[blockOperation addExecutionBlock:^{
[collectionView insertItemsAtIndexPaths:@[newIndexPath]];
}];
}
} else {
shouldReloadCollectionView = YES;
}
break;
}
case NSFetchedResultsChangeDelete: {
if ([self.collectionView numberOfItemsInSection:indexPath.section] == 1) {
shouldReloadCollectionView = YES;
} else {
[blockOperation addExecutionBlock:^{
[collectionView deleteItemsAtIndexPaths:@[indexPath]];
}];
}
break;
}
case NSFetchedResultsChangeUpdate: {
[blockOperation addExecutionBlock:^{
[collectionView reloadItemsAtIndexPaths:@[indexPath]];
}];
break;
}
case NSFetchedResultsChangeMove: {
[blockOperation addExecutionBlock:^{
[collectionView moveItemAtIndexPath:indexPath toIndexPath:newIndexPath];
}];
break;
}
default:
break;
}
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
// Checks if we should reload the collection view to fix a bug @ http://openradar.appspot.com/12954582
if (shouldReloadCollectionView) {
[self.collectionView reloadData];
} else {
[self.collectionView performBatchUpdates:^{
[blockOperation start];
} completion:nil];
}
}
Le mérite de cette approche revient à Blake Watters.
Voici une réponse non piratée, basée sur des documents, au problème. Dans mon cas, il y avait une condition selon laquelle je retournerais une vue supplémentaire valide ou nulle de collectionView:viewForSupplementaryElementOfKind:atIndexPath:
. Après avoir rencontré l'accident, j'ai vérifié les documents et voici ce qu'ils disent:
Cette méthode doit toujours renvoyer un objet de vue valide. Si vous ne souhaitez pas de vue supplémentaire dans un cas particulier, votre objet de présentation ne doit pas créer les attributs de cette vue. Vous pouvez également masquer les vues en définissant la propriété masquée des attributs correspondants sur OUI ou définir la propriété alpha des attributs sur 0. Pour masquer les vues d'en-tête et de pied de page dans une disposition de flux, vous pouvez également définir la largeur et la hauteur de ces vues à 0.
Il existe d'autres façons de procéder, mais la plus rapide semble être:
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewFlowLayout *)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section {
return <condition> ? collectionViewLayout.headerReferenceSize : CGSizeZero;
}
Mon affichage de collection récupérait des éléments à partir de deux sources de données et les mettre à jour provoquait ce problème. Ma solution de contournement consistait à mettre en file d'attente la mise à jour des données et le rechargement de la vue de collection ensemble:
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
//Update Data Array
weakSelf.dataProfile = [results lastObject];
//Reload CollectionView
[weakSelf.collectionView reloadItemsAtIndexPaths:@[[NSIndexPath indexPathForItem:0 inSection:0]]];
}];
Voici une solution pour ce bug que j'ai utilisé dans mes projets que je pensais publier ici au cas où quelqu'un le trouverait utile.
@interface FetchedResultsViewController ()
@property (nonatomic) NSMutableIndexSet *sectionsToAdd;
@property (nonatomic) NSMutableIndexSet *sectionsToDelete;
@property (nonatomic) NSMutableArray *indexPathsToAdd;
@property (nonatomic) NSMutableArray *indexPathsToDelete;
@property (nonatomic) NSMutableArray *indexPathsToUpdate;
@end
#pragma mark - NSFetchedResultsControllerDelegate
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
{
[self resetFetchedResultControllerChanges];
}
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type
{
switch(type)
{
case NSFetchedResultsChangeInsert:
[self.sectionsToAdd addIndex:sectionIndex];
break;
case NSFetchedResultsChangeDelete:
[self.sectionsToDelete addIndex:sectionIndex];
break;
}
}
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject
atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type
newIndexPath:(NSIndexPath *)newIndexPath
{
switch(type)
{
case NSFetchedResultsChangeInsert:
[self.indexPathsToAdd addObject:newIndexPath];
break;
case NSFetchedResultsChangeDelete:
[self.indexPathsToDelete addObject:indexPath];
break;
case NSFetchedResultsChangeUpdate:
[self.indexPathsToUpdate addObject:indexPath];
break;
case NSFetchedResultsChangeMove:
[self.indexPathsToAdd addObject:newIndexPath];
[self.indexPathsToDelete addObject:indexPath];
break;
}
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
if (self.sectionsToAdd.count > 0 || self.sectionsToDelete.count > 0 || self.indexPathsToAdd.count > 0 || self.indexPathsToDelete > 0 || self.indexPathsToUpdate > 0)
{
if ([self shouldReloadCollectionViewFromChangedContent])
{
[self.collectionView reloadData];
[self resetFetchedResultControllerChanges];
}
else
{
[self.collectionView performBatchUpdates:^{
if (self.sectionsToAdd.count > 0)
{
[self.collectionView insertSections:self.sectionsToAdd];
}
if (self.sectionsToDelete.count > 0)
{
[self.collectionView deleteSections:self.sectionsToDelete];
}
if (self.indexPathsToAdd.count > 0)
{
[self.collectionView insertItemsAtIndexPaths:self.indexPathsToAdd];
}
if (self.indexPathsToDelete.count > 0)
{
[self.collectionView deleteItemsAtIndexPaths:self.indexPathsToDelete];
}
for (NSIndexPath *indexPath in self.indexPathsToUpdate)
{
[self configureCell:[self.collectionView cellForItemAtIndexPath:indexPath]
atIndexPath:indexPath];
}
} completion:^(BOOL finished) {
[self resetFetchedResultControllerChanges];
}];
}
}
}
// This is to prevent a bug in UICollectionView from occurring.
// The bug presents itself when inserting the first object or deleting the last object in a collection view.
// http://stackoverflow.com/questions/12611292/uicollectionview-assertion-failure
// This code should be removed once the bug has been fixed, it is tracked in OpenRadar
// http://openradar.appspot.com/12954582
- (BOOL)shouldReloadCollectionViewFromChangedContent
{
NSInteger totalNumberOfIndexPaths = 0;
for (NSInteger i = 0; i < self.collectionView.numberOfSections; i++)
{
totalNumberOfIndexPaths += [self.collectionView numberOfItemsInSection:i];
}
NSInteger numberOfItemsAfterUpdates = totalNumberOfIndexPaths;
numberOfItemsAfterUpdates += self.indexPathsToAdd.count;
numberOfItemsAfterUpdates -= self.indexPathsToDelete.count;
BOOL shouldReload = NO;
if (numberOfItemsAfterUpdates == 0 && totalNumberOfIndexPaths == 1)
{
shouldReload = YES;
}
if (numberOfItemsAfterUpdates == 1 && totalNumberOfIndexPaths == 0)
{
shouldReload = YES;
}
return shouldReload;
}
- (void)resetFetchedResultControllerChanges
{
[self.sectionsToAdd removeAllIndexes];
[self.sectionsToDelete removeAllIndexes];
[self.indexPathsToAdd removeAllObjects];
[self.indexPathsToDelete removeAllObjects];
[self.indexPathsToUpdate removeAllObjects];
}
Dans mon cas, le problème était la façon dont je créais mon NSIndexPath
. Par exemple, pour supprimer la 3e cellule, au lieu de faire:
NSIndexPath* indexPath = [NSIndexPath indexPathWithIndex:2];
[_collectionView deleteItemsAtIndexPaths:@[indexPath]];
J'ai du faire :
NSIndexPath* indexPath = [NSIndexPath indexPathForItem:2 inSection:0];
[_collectionView deleteItemsAtIndexPaths:@[indexPath]];
Vérifiez que vous renvoyez la valeur correcte dans numberOfSectionsInCollectionView:
La valeur que j'utilisais pour calculer les sections était nulle, donc 0
sections. Cela a provoqué l'exception.
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
NSInteger sectionCount = self.objectThatShouldHaveAValueButIsActuallyNil.sectionCount;
// section count is wrong!
return sectionCount;
}
J'ai également rencontré ce problème. Voici ce qui m'est arrivé:
initWithCollectionViewLayout:
, Initialisais mon NSFetchedResultsController
.NSManagedObjects
, ajoutez-le à mon NSManagedObjectContext
, qui a le NSManagedObjectContext
du thread principal comme parentContext
.NSFetchedResultsController
de récupérer les modifications et de les mettre en file d'attente.- (void)controllerDidChangeContent:
, je traiterais les modifications et les appliquerais à mon UICollectionView
.Par intermittence, j'obtiendrais l'erreur que l'OP obtient et je ne pouvais pas comprendre pourquoi.
Pour résoudre ce problème, j'ai déplacé l'initialisation NSFetchedResultsController
et performFetch
vers ma méthode - viewDidLoad
Et ce problème a maintenant disparu. Pas besoin d'appeler [collectionView reloadData]
Ou quoi que ce soit et toutes les animations fonctionnent correctement.
J'espère que cela t'aides!
Il semble que le problème se produit lorsque vous insérez ou déplacez une cellule dans une section qui contient une vue d'en-tête ou de pied de page supplémentaire (avec UICollectionViewFlowLayout
ou une disposition dérivée de cela) et la section a un nombre de cellules avant l'insertion/le déplacement.
Je ne pouvais contourner le crash et maintenir les animations qu'en ayant une cellule vide et invisible dans la section contenant la vue d'en-tête supplémentaire comme ceci:
- (NSInteger)collectionView:(UICollectionView *)view numberOfItemsInSection:(NSInteger)section
retourne la cellule réelle `count + 1 pour la section où se trouve la vue d'en-tête.Dans - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
retour
if ((indexPath.section == YOUR_SECTION_WITH_THE_HEADER_VIEW) && (indexPath.item == [self collectionView:collectionView numberOfItemsInSection:indexPath.section] - 1)) {
cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"EmptyCell" forIndexPath:indexPath];
}
... une cellule vide pour cette position. N'oubliez pas d'enregistrer la réutilisation des cellules dans viewDidLoad
ou partout où vous initialisez votre UICollectionView:
[self.collectionView registerClass:[UICollectionReusableView class] forCellWithReuseIdentifier:@"EmptyCell"];
moveItemAtIndexPath:
ou insertItemsAtIndexPaths:
Sans crash.Vérifiez que vous renvoyez le nombre correct d'éléments dans les méthodes UICollectionViewDataSource
:
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
et
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
Juste pour mémoire, j'ai rencontré le même problème et pour moi la solution était de supprimer l'en-tête (les désactiver dans le .xib) et comme ils n'étaient plus nécessaires, cette méthode a été supprimée. Après cela, tout va bien.
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementary
ElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
J'ai moi-même rencontré ce problème. Toutes les réponses ici semblaient poser des problèmes, à l'exception d'Alex L. Le renvoi de la mise à jour semblait être la réponse. Voici ma dernière solution:
- (void)addItem:(id)item {
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
if (!_data) {
_data = [NSMutableArray arrayWithObject:item];
} else {
[_data addObject:item];
}
[_collectionView insertItemsAtIndexPaths:@[[NSIndexPath indexPathForItem:_data.count-1 inSection:0]]];
}];
}
La solution de contournement qui fonctionne réellement est de renvoyer une hauteur de 0 si la cellule sur le chemin d'index de votre vue supplémentaire n'est pas là (chargement initial, vous avez supprimé la ligne, etc.). Voir ma réponse ici: