Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Invalid update: invalid number of items on UICollectionView

I am stumped on this. Here is my scenario. In my Appdelegate, I am creating

  1. An instance of a view controller that will be presented modally to collect two pieces of data from the user
  2. A tableView controller
  3. A navigation controller that I init with the tableview controller as its rootview
    controller
  4. A collectionviewController, note that I am registering the class for the cell here as well.
  5. A UITabBarController that I add the navigation controller to as the first view and the
    collectionview to as the second view.. The TabBarCOntorller is set as the rootview of the window.

Here is the 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
}

The tableview has a navbar button that presents the model view controller. The modal view controller has a button that takes the values from a textField and a datepicker and posts that to the default notification center with the values in the UserInfo dictionary. The tableView contorller subscribes to the notification, puts the UserInfo dictionary in a MutableArray and updates the table. That works fine.

The problem is with the CollectionVIew. The collectionVIew receives the notification and calls a target method to handle the notification. I am trying to take the UserInfo dictionary from the notification and add a new cell to the collectionView.

BUT...

When the collectionview receives the notification I get the error:

'Invalid update: invalid number of items in section 0. The number of items contained in an existing section after the update (1) must be equal to the number of items contained in that section before the update (1), plus or minus the number of items inserted or deleted from that section (1 inserted, 0 deleted) and plus or minus the number of items moved into or out of that section (0 moved in, 0 moved out).'

Here is the code for the 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

I would so much appreciate anyone who can point anything out here.....

like image 633
Tommy Alexander Avatar asked Oct 05 '13 16:10

Tommy Alexander


3 Answers

It's a bug with using insertItemsAtIndexPaths on an empty UICollectionView. Just do this:

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

(Can't believe this is still broken in iOS8.)

like image 65
Timothy Moose Avatar answered Nov 13 '22 19:11

Timothy Moose


It is a bug in UICollectionView even in iOS 11, but not exist in UITableView, very easy to reproduce: Assume the following SWIFT code is working:

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

Change it to:

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

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

It will crash...

UICollectionView will lose track of the original number of items in some condition, just add a dummy line before insertItems command will avoid this kind of crash:

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)])

Hope it could help in your situation.

like image 33
Michael Su Avatar answered Nov 13 '22 20:11

Michael Su


I had the same error when I've inserted elements in a collectionView with only one section.

I've done the Timothy fix, but it was not enough. In fact, the collectionView had already refreshed its data when I tried to insert new elements. Using collectionView numberOfItemsInSection solves the problem.

[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];
like image 13
vmeyer Avatar answered Nov 13 '22 20:11

vmeyer