I want to delete a cell from a UICollectionView. While deleting cell I get
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of items in section 0. The number of items contained in an existing section after the update (5) must be equal to the number of items contained in that section before the update (5), plus or minus the number of items inserted or deleted from that section (0 inserted, 1 deleted) and plus or minus the number of items moved into or out of that section (0 moved in, 0 moved out).' Error.
Here is my code:
[self.imgArray removeObjectAtIndex:indexForDelete];
[self.collectionView performBatchUpdates:^{
NSIndexPath *indexPath =[NSIndexPath indexPathForItem:indexForDelete inSection:0];
[self.collectionView deleteItemsAtIndexPaths:[NSArray arrayWithObject:indexPath]];
} completion:^(BOOL finished) {
}];
I am appending one dummy cell when my array count is not 5
Here is code for numberOfItems in numberOfItemsInSection
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
if(self.imgArray.count == 5)
{
return self.imgArray.count ;
}
else
{
return self.imgArray.count + 1;
}
}
I found many solution on Google and Stackoverflow but nothing found help full for me.
performBatchUpdates mainly used to perform actions for multiple cells. i.e. delete, insert, move.
Try this,
[self.collectionObj performBatchUpdates:^{
[self.imgArray removeObjectAtIndex:indexForDelete];
NSIndexPath *indexPath =[NSIndexPath indexPathForItem:indexForDelete inSection:0];
[self.collectionView deleteItemsAtIndexPaths:[NSArray arrayWithObject:indexPath]];
[self.collectionView reloadData];
} completion:^(BOOL finished) {
}];
You can also try directly for 1 cell delete without using performBatchUpdates
[self.imgArray removeObjectAtIndex:indexForDelete];
NSIndexPath *indexPath =[NSIndexPath indexPathForItem:indexForDelete inSection:0];
[self.collectionView deleteItemsAtIndexPaths:[NSArray arrayWithObject:indexPath]];
[self.collectionView reloadData];
Hope, this will help you.
Thanks
I think you may need to separate the update into two stages to avoid the NSInternalInconsistencyException.
#property (assign, nonatomic) BOOL isMyCollectionViewUpdating;
//...
[self.imgArray removeObjectAtIndex:indexForDelete];
[self.collectionView performBatchUpdates:^{
self.isMyCollectionViewUpdating = YES;
NSIndexPath *indexPath =[NSIndexPath indexPathForItem:indexForDelete inSection:0];
[self.collectionView deleteItemsAtIndexPaths:[NSArray arrayWithObject:indexPath]];
} completion:^(BOOL finished) {
self.isMyCollectionViewUpdating = NO;
[self.collectionView reloadData];
}];
and for the collection view numberOfItemInSection
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
if (self.isMyCollectionViewUpdating)
{
return self.imgArray.count;
} else {
if(self.imgArray.count == 5)
{
return self.imgArray.count ;
}
else
{
return self.imgArray.count + 1;
}
}
}
Related
I have a horizontal UICollectionView that implements UICollectionViewLayout and uses targetContentOffsetForProposedContentOffset for a custom fling/paging functionality. Each cell is nearly the width of the screen with the next cell showing slightly to the right. The functionality works great, but I want to be able to delete the cell at index 0 when the current cell is index 1. I'm currently able to calculate and do this just fine, but upon deleting the index 0 cell it slides to the next cell (old index 2, new 1 (after deleting 0)) because of the current content offset. I'm not sure how I can delete index 0 while maintaining the current layout.
Right now in my delegate I'm doing:
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{
float cellWidth = self.collectionView.bounds.size.width - 20;
float currentPage = self.collectionView.contentOffset.x / cellWidth;
if (currentPage == 1) {
[self remove:0];
}
}
-(void)remove:(int)i {
[self.collectionView performBatchUpdates:^{
[self.data removeObjectAtIndex:0];
NSIndexPath *indexPath =[NSIndexPath indexPathForRow:0 inSection:0];
[self.collectionView deleteItemsAtIndexPaths:[NSArray arrayWithObject:indexPath]];
} completion:^(BOOL finished) {
}];
}
So the calculation and deletion works fine, but upon deleting [0], the collection view is scrolled to the next cell..and I'm not sure how to stop it.
I've tried self.collectionview.contentOffset = CGMakePoint(0,0) after the deleting, but the transition is still noticeably buggy. Any ideas on how to approach this problem?
so after a ton of trial and error I was able to resolve the issue. It might be a little hackish but I just wrapped the batch with a uiview animation that has no duration. Basically it overrides the deletion animation
[UIView animateWithDuration:0 animations:^{
[self.collectionView performBatchUpdates:^{
[scrollView setContentOffset:CGPointMake(0, 0) animated:NO];
[self.data removeObjectAtIndex:0];
NSIndexPath *indexPath =[NSIndexPath indexPathForRow:0 inSection:0];
[self.collectionView deleteItemsAtIndexPaths:#[indexPath]];
[self.collectionView.collectionViewLayout invalidateLayout];
} completion:nil];
}];
- (void)registerCellCallBack:(id<CellCallBack>)callBack {
int i = 0;
for(; i < _dataSource.count; ++i) {
id<CellCallBack> oldCallBack = _dataSource[i];
if([callBack key] <= [oldCallBack key]) {
break;
}
}
if(i < _dataSource.count) {
[_dataSource insertObject:callBack atIndex:i];
} else {
[_dataSource addObject:callBack];
}
[self.table beginUpdates];
[self.table insertRowsAtIndexPaths:#[[NSIndexPath indexPathForRow:i inSection:0]] withRowAnimation:UITableViewRowAnimationNone];
[self.table endUpdates]; //--crashed here called "NSInternalInconsistencyException(SIGABRT)"
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return _dataSource.count;
}
The code which even was well in iOS9, crashed in iOS10, but not appear when I debug my code.
Crash info:
Invalid update: invalid number of rows in section 0. The
number of rows contained in an existing section after the update (7)
must be equal to the number of rows contained in that section before
the update (0), plus or minus the number of rows inserted or deleted
from that section (1 inserted, 0 deleted) and plus or minus the number
of rows moved into or out of that section (0 moved in, 0 moved out).
You need to change as per below:
To
[self.table reloadData];
From
[self.table beginUpdates];
[self.table insertRowsAtIndexPaths:#[[NSIndexPath indexPathForRow:i inSection:0]] withRowAnimation:UITableViewRowAnimationNone];
[self.table endUpdates]; //--crashed here called "NSInternalInconsistencyException(SIGABRT)"
I have a UITableView with expandable sections. When a user goes to another view, I need all the expanded sections to collapse, which I'll need to put in the viewWillDisappear method.
I've found solutions only on how to delete all rows from a table view at once, but is there a way to delete all the rows from a specific section?
EDIT:
I have figured out a solution, but I'm not sure if it's optimal or can lead to inefficiencies in the future. Whenever a cell is expanded, it gets added to an NSMutableIndexSet. So in my viewWillDisappear method, I iterate over the expanded sections like so:
-(void)viewWillDisappear:(BOOL)animated
{
if (expandedSections.count != 0) {
NSLog(#"COLLAPSING CALLED");
[self.tableView beginUpdates];
NSUInteger section = [expandedSections firstIndex];
do
{
NSInteger rows;
NSMutableArray *tmpArray = [NSMutableArray array];
rows = [self tableView:self.tableView numberOfRowsInSection:section];
[expandedSections removeIndex:section];
for (int i=1; i<rows; i++) {
NSIndexPath *tmpIndexPath = [NSIndexPath indexPathForRow:i inSection:section];
[tmpArray addObject:tmpIndexPath];
}
[self.tableView deleteRowsAtIndexPaths:tmpArray withRowAnimation:UITableViewRowAnimationTop];
NSIndexPath *expandableCellIndexPath = [NSIndexPath indexPathForRow:0 inSection:section];
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:expandableCellIndexPath];
cell.accessoryView = [DTCustomColoredAccessory accessoryWithColor:[self.colorHolder objectAtIndex:section] type:DTCustomColoredAccessoryTypeRight];
section = [expandedSections indexGreaterThanIndex:section];
} while (section != NSNotFound);
[self.tableView endUpdates];
}
}
Please let me know if this is a good solution or, if I'm suspecting correctly, if this will lead to slower transitions between views in the future when there are more rows in each expanded section. Any help or advice would be appreciated. Thanks.
If you want to animate changes, you will need to first update your data source (to return 0 for number of rows in the section) then remove section and add section at the same index path in one transaction between [tv beginUpdates] [tv endUpdates]
Otherwise just update the data source and reload the table on your way back to the VC (if you don't want any animations)
I did not read this in detail but surely the for loop should start at zero.
for (int i=0; i<rows; i++) {
NSIndexPath *tmpIndexPath = [NSIndexPath indexPathForRow:i inSection:section];
[tmpArray addObject:tmpIndexPath];
}
otherwise you will only delete all but the first cells in the section.
Hi I'm working with a Custom UICollectionView (https://github.com/SureCase/WaterfallCollectionView) where everything works fine. Now I'm setting up delete items from the UICollectionView, and I can delete them fine. The problem comes when I'm trying to delete the last item of the section.
It gives the following error.
*** Assertion failure in -[UICollectionViewData layoutAttributesForSupplementaryElementOfKind:atIndexPath:], /SourceCache/UIKit/UIKit-2935.137/UICollectionViewData.m:787
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'no UICollectionViewLayoutAttributes instance for -layoutAttributesForSupplementaryElementOfKind: UICollectionElementKindSectionHeader at path <NSIndexPath: 0xc000000000000016> {length = 2, path = 0 - 0}'
The Code I'm using to delete items is the following:
- (void) alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
if (buttonIndex == 0){
NSLog(#"Erasing Objects!");
//First I remove Items from DataSource, and after from Collection View
[self deleteItemsFromDataSourceAtIndexPaths:selectedIndexes];
[self.cv deleteItemsAtIndexPaths:selectedIndexes];
[selectedIObjectsIndexes removeAllObjects];
[selectedIndexes removeAllObjects];
EditUICollection=NO;
EasyMediaGreenView.hidden = YES;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.7 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
[self.cv reloadData];
});
}else{
NSLog(#"Not delete");
}
}
So first I'm removing items from DataSource and then from de Collection View. Here the Code for removing from Data Source.
-(void)deleteItemsFromDataSourceAtIndexPaths:(NSArray *)itemPaths
{
NSMutableIndexSet *indexSet = [NSMutableIndexSet indexSet];
for (NSIndexPath *itemPath in itemPaths) {
[indexSet addIndex:itemPath.row];
}
[idObject removeObjectsAtIndexes:indexSet];
[typeObject removeObjectsAtIndexes:indexSet];
[urlObject removeObjectsAtIndexes:indexSet];
[textObject removeObjectsAtIndexes:indexSet];
}
Why is this happening when I try to remove last object, when all other objects are removed correctly? I would like to understand this part, & any idea how to fix this? Thanks to all.
I Found a solution!
I was always returning 1 for section, so even with no items the CollectionView was being constructed, and asking to build a Header, like this:
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView
viewForSupplementaryElementOfKind:(NSString *)kind
atIndexPath:(NSIndexPath *)indexPath; {
So I counted the items in my data array, and if the array is empty, there shouldn't be a section.
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
if(idObject.count>0){ return 1; }
else{ return 0; }
}
So where I'm deleting the items from my data array, I can check if the array is empty, if is not empty I'll delete items, if it is, I'll delete the entire section.
NSLog(#"Erasing Objects!");
[self deleteItemsFromDataSourceAtIndexPaths:selectedIndexes];
if(idShadeInside.count > 0){
[self.cv deleteItemsAtIndexPaths:selectedIndexes];
}
else{
//Creating an IndexSet with the Section to Erase
NSMutableIndexSet *indexSet = [NSMutableIndexSet indexSet];[indexSet addIndex:0];
[self.cv deleteSections:indexSet];
}
So problem fixed!
i have done this features, here i provides you a simple code to delete cell from Collection View.
Just pass you index in integer here.
-(void)remove:(int)i {
#try {
[self.collection performBatchUpdates:^{
[self.arrayThumbImg removeObjectAtIndex:i];
NSIndexPath *indexPath =[NSIndexPath indexPathForRow:i inSection:0];
[self.collection deleteItemsAtIndexPaths:[NSArray arrayWithObject:indexPath]];
} completion:^(BOOL finished) {
}];
}
#catch (NSException *exception) {
NSLog(#"Error: %#",exception);
}
#finally {
}
}
I've got a UITableView that presents some settings to the user. Some cells are hidden unless a UISwitch is in the 'On' position. I've got the following code:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return switchPush.on ? 6 : 1;
}
// Hooked to the 'Value Changed' action of the switchPush
- (IBAction)togglePush:(id)sender {
NSMutableArray *indexPaths = [NSMutableArray arrayWithCapacity:0];
for(int i = 1; i <= 5; i++) {
[indexPaths addObject:[NSIndexPath indexPathForRow:i inSection:0]];
}
[tableView beginUpdates];
if(switchPush.on) {
[tableView insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationAutomatic];
} else {
[tableView deleteRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationAutomatic];
}
[tableView endUpdates];
}
This works as expected, until I switch the UISwitch twice in rapid succession (by double tapping), in which case the application crashes with a
Invalid table view update. The application has requested an update to the table view that is inconsistent with the state provided by the data source.
I know that it is caused by the wrong return value of numberOfRowsInSection as the switch is back in its original position while the cell animation is still playing. I've tried disabling the toggle and hooking the code under other event handlers but nothing seems to prevent the crash. Using reloadData instead of the animation also solves to problem but I would prefer the nice animations.
Does anybody know a correct way of implementing this?
Another (more elegant) solution at the problem is this:
I modified the Alan MacGregor - (IBAction)SwitchDidChange:(id)sender method in this way:
- (IBAction)SwitchDidChange:(UISwitch *)source {
if (_showRows != source.on) {
NSArray *aryTemp = [[NSArray alloc] initWithObjects:
[NSIndexPath indexPathForRow:1 inSection:0],
[NSIndexPath indexPathForRow:2 inSection:0],
[NSIndexPath indexPathForRow:3 inSection:0],
[NSIndexPath indexPathForRow:4 inSection:0],nil];
[_tblView beginUpdates];
_showRows = source.on;
if (_showRows) {
[_tblView insertSections:aryTemp withRowAnimation:UITableViewRowAnimationFade];
}
else {
[_tblView deleteSections:aryTemp withRowAnimation:UITableViewRowAnimationFade];
}
[_tblView endUpdates];
}
}
The other parts stay unchanged.
Simply set the enabled property of your switch to NO until the updates are done.
I had this issue on mine and the way to avoid the crash is to not explicitly use the uiswitch, instead relay the information into a boolean, heres how I did it.
Add a boolean to the top of your implementation file
bool _showRows = NO;
Update your uiswitch code
- (IBAction)SwitchDidChange:(id)sender {
NSArray *aryTemp = [[NSArray alloc] initWithObjects:[NSIndexPath indexPathForRow:1 inSection:0],
[NSIndexPath indexPathForRow:2 inSection:0],
[NSIndexPath indexPathForRow:3 inSection:0],
[NSIndexPath indexPathForRow:4 inSection:0],nil];
if (_showRows) {
_showRows = NO;
_switch.on = NO;
[_tblView deleteRowsAtIndexPaths:aryTemp withRowAnimation:UITableViewRowAnimationTop];
}
else {
_showRows = YES;
_switch.on = YES;
[_tblView insertRowsAtIndexPaths:aryTemp withRowAnimation:UITableViewRowAnimationBottom];
}
}
And finally update your numberOfRowsInSection
- (NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section
{
if (section == 0) {
if (_showRows) {
return 5;
}
else {
return 1;
}
}
return 0;
}
UIControlEventValueChanged events occur even when a control's value doesn't actually change. so togglePush gets called even when the value of the switch doesn't change. when you quickly toggle the switch, you might not always go from on > off > on > off, etc. it's possible to go off > on > on > off.
so what's happening is that you're getting two ons in a row causing two insertSections one after the other. which is obviously bad.
to fix this, you need to remember what the previous state of the button was (in an ivar, maybe) and only perform the insert (or delete) if the new value (source.on) is different from the previous value.
So I also had this issue. You have to disable the UISwitch as soon as change your value. Then you do your updates inside the performBatchUpdates(iOS 11 and above!). As soon as your updates are finished you enable the UISwitch in the callback.
//...method with value change or other control event:
yourUISwitch.enabled = NO;
//...method with your tableview inserts or deletes:
[self.tableView beginUpdates];
[self.tableView performBatchUpdates:^{
[self.tableView insertRowsAtIndexPaths:yourIndexPath withRowAnimation:UITableViewRowAnimationFade];
} completion:^(BOOL finished) {
if(finished){
yourUISwitch.enabled = YES;
}
}];
[self.tableView endUpdates];