Reputation: 701
I am stumped on this. Here is my scenario. In my Appdelegate, I am creating
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.....
Upvotes: 39
Views: 45255
Reputation: 19612
In my situation I'm loading videos so running collectionView.reloadData()
won't work, I have to use the insert
method to add each video to the bottom of the cv.
I ran into a weird problem where I only have 1 section. The below code worked fine while I was on the current tab and the view was visible:
datasource.append(post)
let lastIndex = datasource.count - 1
let indexPath = IndexPath(item: lastIndex, section: 0)
collectionView.performBatchUpdates({
self.collectionView.insertItems(at: [indexPath])
})
But if the view was no longer visible because I switched tabs, when the above code would run (it stills runs even if I'm on another tab), I would get the Invalid update: invalid number of items on UICollectionView
error.
I tried every answer on here but none of them worked. Even the ones that said to use cv.reloadData()
. The only thing that worked was when I followed this ray wenderlich answer by nikita_gaydukov. He said to do everything inside performBatchUpdates
. It worked because the error was gone!
collectionView.performBatchUpdates({
self.datasource.append(post)
let lastIndex = self.datasource.count - 1
let indexPath = IndexPath(item: lastIndex, section: 0)
self.collectionView.insertItems(at: [indexPath])
})
Upvotes: 1
Reputation: 33
As the above, to avoid this issue, you can do this:
1. when reload collectionView:
[self.collectionView reloadData];
[self.collectionView layoutIfNeeded];
2. when invoke the batch updates:
[self.collectionView performBatchUpdates:^{
...
[self.dataSource addDataSource:moreDatas];
NSMutableArray *addIndexPaths;
...
if (self.dataSource.count == 1 ||
[self.collectionView numberOfItemsInSection:0] == self.dataSource.count){
[self.collectionView reloadData];
}else{
[self.collectionView insertItemsAtIndexPaths:addIndexPaths];
}
} completion:^(BOOL finished) {}];
Upvotes: 0
Reputation: 502
I'm not practice with Obj-c so I write my answer in Swift.
supposing birthdays is an array of dates as String
birthdays = [String]()
func addDate() {
birthdays.append("your_new_date")
collectionView.insertItems(at: [IndexPath(item: birthdays.count - 1, section: 0)] )
}
func removeDate() {
if birthdays.count > 0 {
birthdays.removeLast() // use remove(at: Int) instead if u want
collectionView.deleteItems(at: [IndexPath(item: birthdays.count, section: 0)] )
}
}
You should avoid this error:
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).'
Upvotes: 0
Reputation: 2626
As for me the issue was connected to the Index Set - where to insert sections. 1. I've inserted Sections 0..<10 (10 sections) 2. I was trying to insert Sections to IndexSet 9..<19. But appending the new objects to the end of the dataSource array. Correct Index set should be (10..<20)
Thus the last section was still expecting the number of rows from Section 9. But in data source - it was at index 19.
collectionView?.insertSections(IndexSet(integersIn: startIndex..<(startIndex + series.count)))
Upvotes: 0
Reputation: 341
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.
Upvotes: 34
Reputation: 1846
In my case, numberOfItemsInSection
was being called twice from self.performBatchUpdates
(once for the BEFORE number, once for the AFTER number) because I was trying to insert something into the collectionView before the view controller had finished loading.
The solution is to add guard isViewLoaded else { return }
before calling self.performBatchUpdates
.
Upvotes: 6
Reputation: 2038
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];
Upvotes: 14
Reputation: 9915
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.)
Upvotes: 69