UICollectionView Assertion failure

je m d'erreur sur l'exécution de insertItemsAtIndexPaths dans UICollectionView

échec de l'Assertion:

-[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. Pourquoi cela pourrait se produire? Si vous avez besoin de plus d'informations, je peux vous les fournir.

59
demandé sur Shmidt 2012-09-27 02:16:56

14 réponses

j'ai rencontré ce même problème en insérant la première cellule dans une vue collection. J'ai corrigé le problème en changeant mon code pour appeler UICollectionView

- (void)reloadData

méthode 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 du la dernière cellule. J'ai fait la même chose qu'avant: il suffit d'appeler reloadData lors de la suppression de la dernière cellule.

68
répondu Jay Slupesky 2012-10-19 18:08:56

insérer la section # 0 juste avant d'insérer des cellules semble rendre UICollectionView heureux.

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];
11
répondu neoneye 2013-03-18 23:17:13

j'ai posté un article pour ce numéro ici: https://gist.github.com/iwasrobbed/5528897

dans la catégorie privé en haut de votre fichier .m :

@interface MyViewController ()
{
    BOOL shouldReloadCollectionView;
    NSBlockOperation *blockOperation;
}
@end

alors vos callbacks de délégué serait:

- (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];
    }
}

crédit pour cette approche va à Blake Watters.

5
répondu iwasrobbed 2013-06-12 03:12:48

Voici une réponse non-hack, basée sur docs au problème. Dans mon cas, il y avait une condition selon laquelle je retournerais une vue valide ou une vue supplémentaire nulle de collectionView:viewForSupplementaryElementOfKind:atIndexPath: . Après avoir rencontré le crash, j'ai vérifié les documents et voici ce qu'ils disent:

cette méthode doit toujours retourner un objet de vue valide. Si vous ne voulez pas une vue supplémentaire dans un cas particulier, votre objet layout devrait ne pas créer les attributs pour cette vue. Alternativement, vous pouvez masquer vues en définissant la propriété cachée des attributs correspondants OUI ou régler la transparence de la propriété des attributs à 0. Cacher vues d'en-tête et de pied de page dans une disposition de flux, vous pouvez également définir la largeur et hauteur de ces vues à 0.

il y a d'autres façons de le faire, mais la plus rapide semble être:

- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewFlowLayout *)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section {
    return <condition> ? collectionViewLayout.headerReferenceSize : CGSizeZero;
}
4
répondu Victor Bogdan 2013-09-20 09:53:36

mon point de vue sur la collecte était d'obtenir des éléments à partir de deux sources de données et leur mise à jour a causé ce problème. Ma solution était de mettre en file d'attente la mise à jour des données et la vue de collecte recharger ensemble:

[[NSOperationQueue mainQueue] addOperationWithBlock:^{

                //Update Data Array
                weakSelf.dataProfile = [results lastObject]; 

                //Reload CollectionView
                [weakSelf.collectionView reloadItemsAtIndexPaths:@[[NSIndexPath indexPathForItem:0 inSection:0]]];
 }];
3
répondu Alex L 2013-05-01 23:39:20

vérifiez que vous retournez le nombre correct d'éléments dans les méthodes UICollectionViewDataSource :

- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section

et

- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
1
répondu Morrowless 2012-09-27 03:58:48

j'ai rencontré ce problème aussi. Voici ce qui m'est arrivé:

  1. je sous-classé UICollectionViewController et, sur initWithCollectionViewLayout: , a été l'initialisation de mon NSFetchedResultsController .
  2. ont une classe partagée fetch results from an Nslconnection et analysent la chaîne JSON (different thread)
  3. boucle à travers l'alimentation et créer mon NSManagedObjects , les ajouter à mon NSManagedObjectContext , qui a le fil principal NSManagedObjectContext comme un parentContext .
  4. Enregistrer mon contexte.
  5. demandez à mon NSFetchedResultsController de prendre les changements et de les mettre en file d'attente.
  6. sur - (void)controllerDidChangeContent: , je traiterais les modifications et les appliquerais à mon UICollectionView .

de façon intermittente, j'obtiendrais l'erreur que L'opération obtient et je ne pouvais pas comprendre pourquoi.

pour corriger ce problème, j'ai déplacé le NSFetchedResultsController initialisation et performFetch à ma méthode - viewDidLoad et ce problème est maintenant disparu. Pas besoin d'appeler [collectionView reloadData] ou quoi que ce soit et toutes les animations fonctionnent correctement.

Espérons que cette aide!

1
répondu Simon Germain 2013-03-25 17:27:32

il semble que le problème se produit lorsque vous insérez ou déplacez une cellule vers une section qui contient un en-tête supplémentaire ou une vue de pied de page (avec UICollectionViewFlowLayout ou un layout dérivé de cela) et la section a un nombre de 0 cellules avant l'insertion / 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:

  1. Faire - (NSInteger)collectionView:(UICollectionView *)view numberOfItemsInSection:(NSInteger)section retour de la réelle de la cellule `count + 1 pour la section où la vue d'en-tête.
  2. 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 le poste. N'oubliez pas d'enregistrer la réutilisation de la cellule dans viewDidLoad ou partout où vous initialisez votre UICollectionView:

    [self.collectionView registerClass:[UICollectionReusableView class] forCellWithReuseIdentifier:@"EmptyCell"];
    
  3. utiliser moveItemAtIndexPath: ou insertItemsAtIndexPaths: sans s'écraser.
1
répondu Markus Rautopuro 2013-04-22 16:01:43

Voici une solution pour ce bug que j'ai utilisé dans mes projets, je pensais l'afficher ici au cas où l'un d'entre eux l'aurait trouvé 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];
}
1
répondu gdavis 2014-03-07 16:27:18

dans mon cas, le problème était la façon dont je créais mon NSIndexPath . Par exemple, pour supprimer la 3ème cellule, au lieu de faire :

NSIndexPath* indexPath = [NSIndexPath indexPathWithIndex:2];
[_collectionView deleteItemsAtIndexPaths:@[indexPath]];

j'avais besoin de faire:

NSIndexPath* indexPath = [NSIndexPath indexPathForItem:2 inSection:0];
[_collectionView deleteItemsAtIndexPaths:@[indexPath]];
1
répondu Raphael Royer-Rivard 2016-03-18 16:38:39

vérifiez que vous retournez la bonne valeur dans numberOfSectionsInCollectionView:

la valeur que j'utilisais pour calculer les sections était nulle, donc 0 sections. Cela a causé l'exception.

- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
    NSInteger sectionCount = self.objectThatShouldHaveAValueButIsActuallyNil.sectionCount;

    // section count is wrong!

    return sectionCount;
}
1
répondu pkamb 2016-05-24 21:00:39

juste pour info, 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 a supprimé cette méthode. Après que tout semble aller bien.

- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementary
ElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
0
répondu Emilio 2013-04-26 13:36:56

j'ai moi-même eu ce problème. Toutes les réponses ici semblaient présenter des problèmes, sauf celles d'Alex L. Renvoyer la mise à jour semble être la réponse. Voici ma solution finale:

- (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]]];
    }];
}
0
répondu Owen Godfrey 2013-06-18 08:05:05

la solution qui fonctionne réellement est de retourner une hauteur de 0 si la cellule au chemin d'index de votre vue supplémentaire n'est pas là (chargement initial, vous avez supprimé la rangée, etc). Voir ma réponse ici:

https://stackoverflow.com/a/18411860/917104

0
répondu thejav 2017-05-23 12:34:23