web-dev-qa-db-fra.com

Mise à jour non valide: nombre d'éléments non valide sur UICollectionView

Je suis perplexe à ce sujet. Voici mon scénario. Dans mon Appdelegate, je crée

  1. Une instance d'un contrôleur de vue qui sera présentée sous forme modale pour collecter deux données de l'utilisateur
  2. Un contrôleur de table
  3. Un contrôleur de navigation que j'ai initié avec le contrôleur tableview en tant que rootview
    manette
  4. A collectionviewController, notez que je suis également en train d'enregistrer la classe pour la cellule. 
  5. Un UITabBarController auquel j’ajoute le contrôleur de navigation comme première vue et
    collectionview to as the second view .. Le TabBarCOntorller est défini comme la vue racine de la fenêtre. 

Voici le code: 

 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:   
(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.evc = [[WTAEntryControllerViewController alloc] init];
WTATableView *tv = [[WTATableView alloc] initWithNibName:@"WTATableView" bundle:nil];
UINavigationController *nav = [[UINavigationController alloc]    
initWithRootViewController:tv];
nav.tabBarItem.title = NSLocalizedString(@"Birthday List", nil);
nav.tabBarItem.image = [UIImage imageNamed:@"birthdaycake"];
WTACollectionViewController *cv = [[WTACollectionViewController alloc] 
initWithCollectionViewLayout:[[UICollectionViewFlowLayout alloc] init]];
[cv.collectionView registerClass:[UICollectionViewCell class] 
forCellWithReuseIdentifier:CellIdentifier];
cv.collectionView.backgroundColor = [UIColor whiteColor];
NSLog(@"cv instance %@", cv);
UITabBarController *tabController = [[UITabBarController alloc] init];
tabController.viewControllers = @[nav, cv];
self.window.rootViewController = tabController;
[self.window makeKeyAndVisible];
return YES
}

La vue table a un bouton de barre de navigation qui présente le contrôleur de vue modèle. Le contrôleur de vue modale a un bouton qui prend les valeurs d'un textField et d'un datepicker et les poste dans le centre de notification par défaut avec les valeurs du dictionnaire UserInfo. Le observateur tableView souscrit à la notification, place le dictionnaire UserInfo dans un MutableArray et met à jour la table. Cela fonctionne bien. 

Le problème est avec la CollectionVIew. CollectionVIew reçoit la notification et appelle une méthode cible pour gérer la notification. J'essaie de prendre le dictionnaire UserInfo de la notification et d'ajouter une nouvelle cellule à la collectionView. 

MAIS...

Lorsque la collectionview reçoit la notification, j'obtiens l'erreur: 

'Mise à jour non valide: nombre d'éléments non valide dans la section 0. Le nombre d'éléments contenus dans une section existante après la mise à jour (1) doit être égal au nombre d'éléments contenus dans cette section avant la mise à jour (1), plus ou moins le nombre d'éléments insérés ou supprimés de cette section (1 inséré, 0 supprimé) et plus ou moins le nombre d'éléments entrés dans cette section ou sortis de celle-ci (0 déplacé, 0 déplacé). '

Voici le code pour CollectionView:

#import "WTACollectionViewController.h"
#import "UIColor+WTAExtensions.h"
#import "WTAAppDelegate.h"
#import "WTAEntryControllerViewController.h"

@interface WTACollectionViewController () <UICollectionViewDelegateFlowLayout>
@property (strong, nonatomic) NSMutableArray *birthdays;
@end

@implementation WTACollectionViewController

-(id)initWithCollectionViewLayout:(UICollectionViewLayout *)layout{

NSLog(@"Calling init on Collection");
if(!(self = [super initWithCollectionViewLayout:layout])){
    return nil;
}

self.birthdays = [NSMutableArray array];

NSLog(@"Count of Birthdays at init %ld", (long)[self.birthdays count] );
self.title = NSLocalizedString(@"Birthday Grid", nil);
self.tabBarItem.image = [UIImage imageNamed:@"package"];
NSLog(@"cv instance getting notif %@", self);
[[NSNotificationCenter defaultCenter]
 addObserver:self
 selector:@selector(handleNotification:)
 name:@"BirthdayAdded"
 object:nil];
 return self;

}


-(void)handleNotification:(NSNotification *)notification
{
[self.birthdays addObject:[notification userInfo]];
NSLog(@"Count of birthdays when the notification is received: %ld", (long)         
[self.birthdays count]);
[self.collectionView insertItemsAtIndexPaths:@[[NSIndexPath indexPathForItem:   
(self.birthdays.count -1) inSection:0]]];
}

- (void)viewDidLoad
{
[super viewDidLoad];

NSLog(@"Calling View Did Load on Collection");
}

- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {

return 1;

}

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

NSLog(@"Count of birthdays in  the number of items in section: %ld", (long)  
[self.birthdays count]);
return [self.birthdays count];
}

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView    
cellForItemAtIndexPath:(NSIndexPath *)indexPath {

NSLog(@"Calling the cell for index path method");

NSDictionary *dict = [[NSMutableDictionary alloc] init];
dict = self.birthdays[indexPath.item];

UICollectionViewCell *cell = [collectionView   
dequeueReusableCellWithReuseIdentifier:CellIdentifier forIndexPath:indexPath];
NSAssert(cell != nil, @"Expected a Cell");

cell.contentView.backgroundColor = [UIColor randomColor];
return cell;

}

#define GAP (1.0f)

-(CGSize)collectionView:(UICollectionView *)collectionView layout:   
(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath   
*)indexPath {


CGFloat edgeLength = self.collectionView.frame.size.width / 4.0f - GAP;
return (CGSize) {.width = edgeLength, .height = edgeLength};
}

-(CGFloat)collectionView:(UICollectionView *)collectionView layout:   
(UICollectionViewLayout *)collectionViewLayout minimumLineSpacingForSectionAtIndex:    
(NSInteger)section{

return GAP;
}

-(CGFloat)collectionView:(UICollectionView *)collectionView layout:   
(UICollectionViewLayout *)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:  
(NSInteger)section{

return GAP;
}
@end

J'apprécierais tellement quelqu'un qui peut signaler n'importe quoi ici .....

30
Tommy Alexander

C'est un bug avec l'utilisation de insertItemsAtIndexPaths sur une UICollectionView vide. Faites juste ceci:

if (self.birthdays.count == 1) {
    [self.collectionView reloadData];
} else {
    [self.collectionView insertItemsAtIndexPaths:@[[NSIndexPath indexPathForItem:   (self.birthdays.count -1) inSection:0]]];
}

(Je ne peux pas croire que cela est toujours cassé dans iOS8.)

57
Timothy Moose

C’est un bogue dans UICollectionView même dans iOS 11, mais il n’existe pas dans UITableView, il est très facile à reproduire: Supposons que le code Swift suivant fonctionne: 

addItemInDataSource()
collectionView!.insertItems(at: [IndexPath(item: position, section: 0)])

Changez le en:

collectionView!.reloadData()    // <-- This new line will make UICollection crash...

addItemInDataSource()
collectionView!.insertItems(at: [IndexPath(item: position, section: 0)])

Il va s'écraser ...

UICollectionView perdra la trace du nombre original d'éléments dans certaines conditions. Il suffit d'ajouter une ligne factice avant que la commande insertItems évite ce type de blocage:

collectionView!.reloadData()

collectionView!.numberOfItems(inSection: 0) //<-- This code is no used, but it will let UICollectionView synchronize number of items, so it will not crash in following code.
addItemInDataSource()
collectionView!.insertItems(at: [IndexPath(item: position, section: 0)])

J'espère que cela pourrait aider dans votre situation.

19
Michael Su

J'ai eu la même erreur lorsque j'ai inséré des éléments dans une collectionView avec une seule section. 

J'ai corrigé Timothy, mais ce n'était pas suffisant. En fait, collectionView avait déjà actualisé ses données lorsque j'ai essayé d'insérer de nouveaux éléments. Utiliser collectionView numberOfItemsInSection résout le problème. 

[self.collectionView performBatchUpdates:^{
        NSMutableArray *arrayWithIndexPaths = [NSMutableArray array];
        for (NSUInteger i = [self.collectionView numberOfItemsInSection:0]; i < self.elements.count ; i++)
        {
            [arrayWithIndexPaths addObject:[NSIndexPath indexPathForRow:i inSection:0]];
        }

        [self.collectionView insertItemsAtIndexPaths:arrayWithIndexPaths];
    } completion:nil];
12
vmeyer

Dans mon cas, numberOfItemsInSection a été appelé deux fois à partir de self.performBatchUpdates (une fois pour le numéro BEFORE, une fois pour le numéro AFTER) parce que j'essayais d'insérer quelque chose dans collectionView avant que le contrôleur de vue ne soit chargé.

La solution consiste à ajouter guard isViewLoaded else { return } avant d'appeler self.performBatchUpdates.

3
p-sun

Quant à moi, le problème était lié à l'ensemble d'index - où insérer des sections. 1. J'ai inséré les sections 0 .. <10 (10 sections) 2. J'essayais d'insérer des sections dans IndexSet 9 .. <19. Mais en ajoutant les nouveaux objets à la fin du tableau dataSource. L’index correct doit être défini (10 .. <20)

Ainsi, la dernière section s’attendait toujours au nombre de lignes de la section 9. Mais dans la source de données, c’était à l’index 19. 

collectionView? .insertSections (IndexSet (integersIn: startIndex .. <(startIndex + series.count)))

0
Naloiko Eugene