web-dev-qa-db-fra.com

Échec de l'assertion UICollectionView

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.

59
jajo87

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.

68
Jay Slupesky

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];
11
neoneye

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.

5
iwasrobbed

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;
}
4
Victor Bogdan

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]]];
 }];
3
Alex L

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];
}
1
gdavis

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]];
1

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;
}
1
pkamb

J'ai également rencontré ce problème. Voici ce qui m'est arrivé:

  1. J'ai sous-classé UICollectionViewController et, sur initWithCollectionViewLayout:, Initialisais mon NSFetchedResultsController.
  2. Avoir une classe partagée récupérer les résultats d'une NSURLConnection et analyser la chaîne JSON (thread différent)
  3. Parcourez le flux et créez mon NSManagedObjects, ajoutez-le à mon NSManagedObjectContext, qui a le NSManagedObjectContext du thread principal comme parentContext.
  4. Sauvegardez mon contexte.
  5. Demandez à mon NSFetchedResultsController de récupérer les modifications et de les mettre en file d'attente.
  6. Sur - (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!

1
Simon Germain

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:

  1. Make - (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.
  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 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"];
    
  3. Utilisation moveItemAtIndexPath: ou insertItemsAtIndexPaths: Sans crash.
1
Markus Rautopuro

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
1
Morrowless

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
0
PakitoV

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]]];
    }];
}
0
Owen Godfrey

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:

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

0
deepseadiving