Update for code: Following on Matthew's answer I tried correcting my code to be more correct. Now the code does delete the cell but also crashes and gives an error:
* Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '* -[__NSPlaceholderArray initWithObjects:count:]: attempt to insert nil object from objects[0]'
The code below is from an action called checkboxTapped which is in my CustomCell code. Once action is fired it gives the error. I figured out that my indexPath is equal to NULL, and thats most likely the issue. But I don't know how to fix it.
[self.textLabel setTextColor:[UIColor grayColor]];
[self.detailTextLabel setTextColor:[UIColor grayColor]];
parent = [[ViewController alloc] init];
db = [[DataObject alloc] init];
NSIndexPath *indexPath = [[parent tableView] indexPathForSelectedRow];
[[parent array] removeObjectAtIndex:[indexPath row]];
[db deleteTaskAtIndex:[indexPath row]];
[[parent tableView] deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
[db release];
[parent release];
Old: I looked through my code and I printed my array I was using and it appears to be fine, yet this error still persists.
* Terminating app due to uncaught exception 'NSRangeException', reason: '* -[__NSArrayM removeObjectAtIndex:]: index 1 beyond bounds [0 .. 0]'
My guess was that it had something to do with my indexPath but it doesn't make much different how much I change it.
-(void)checkboxTapped:(id)sender
{
[sender setSelected:YES];
[self.textLabel setTextColor:[UIColor grayColor]];
[self.detailTextLabel setTextColor:[UIColor grayColor]];
parent = [[ViewController alloc] init];
UITableView *tableView = parent.tableView;
NSMutableArray *array = [[NSMutableArray alloc] initWithArray:parent.array];
[parent release];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:[array count] inSection:1];
[array removeObjectAtIndex:[indexPath row]];
[db deleteTaskAtIndex:[indexPath row]];
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationTop];
[array release];
[tableView endUpdates];
[tableView reloadData];
}
In your code [indexPath row] is going to return the value of [array count]. That's unlikely to be what you want. If your array has zero objects in it, you are going to attempt to remove the object at index 0. But there will be no objects and you'll get an error. If your array has 1 object in it, you're going to attempt to remove the object at index 1. Again, that will fail, because there is no object at index 1, just one object at index 0.
If you want to remove the last object in an array you need to use an index that is count-1. You may also need to check to see if the array is empty, if that case can occur.
Updated in response to follow up in comment
You don't want to do anything indexPathWithIndex. As a first step, try modifying your code along the following lines:
-(void)checkboxTapped:(id)sender
{
[sender setSelected:YES];
[self.textLabel setTextColor:[UIColor grayColor]];
[self.detailTextLabel setTextColor:[UIColor grayColor]];
parent = [[ViewController alloc] init]; // looks very odd - is an instance of this viewController active when the checkBox is tapped? If so, you don't want to create a new one, you want to access the existing one
UITableView *tableView = parent.tableView;
[parent release]; // this looks very dicey - when you release the parent, won't it release the tableView too?!
int lastRow = [array count] - 1;
if (lastRow == 0)
{
return; // bail if there are no rows in the table
}
NSMutableArray *array = [[NSMutableArray alloc] initWithArray:parent.array];
[array removeObjectAtIndex: lastRow]; // not clear this will do anything as the reference to array is discarded later
[db deleteTaskAtIndex: lastRow];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow: lastRow inSection:1];
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationTop];
[array release];
// [tableView endUpdates]; // there's no matching beginUpdates and you're only do one change operation anyway - leave this out
// [tableView reloadData]; // if you leave this line in, you won't see the delete animation - if you just want to delete one row, you wouldn't normally use reloadData, at least not if you want the animation
}
All this said, it looks as if there are other things going on here.
What's happening with array? You create this, remove an item from it and the discard the pointer to it. Is that what you really want to do. A more common pattern would be to get the pointer to the array from the other object and remove the item at the end of it here.
It's not clear from your code how you are updating the table's data source. When using deleteRowsAtIndexPaths:withRownAnimation you need to make sure the table's data source will return one row less than it did last time it was asked with tableView:numberOfRowsInSection:. From your code it's not clear how the tableView dataSource is going to know there's one less item, unless, perhaps, it's looking at whatever it is that db is pointing to in order to find this out.
More fundamentally, with a typical design pattern the tableView is going to be released when you release the parent view, so whatever it points to after `[parent release]' is going to do something undefined and is likely to crash at least some of the time.
Related
I have the following code
[self.tV beginUpdates];
NSIndexPath *iP = [NSIndexPath indexPathForRow:indexArray.count inSection:0];
[indexArray addObject:iP];
[self.tV insertRowsAtIndexPaths:indexArray withRowAnimation:UITableViewRowAnimationFade];
[self.tV endUpdates];
I get the following error
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason:
'Invalid update: invalid number of rows in section 0. The number of rows contained in an
existing section after the update (2) must be equal to the number of rows contained in
that section before the update (1), plus or minus the number of rows inserted or deleted
from that section (2 inserted, 0 deleted) and plus or minus the number of rows moved into
or out of that section (0 moved in, 0 moved out).'
I'm not sure where the 2 inserted is coming from. This code is called every time I click a button. The first time there is one element in indexArray, as seen in the code I add one more element but it seems as if it is trying to add both elements again. Is that correct?
UPDATE
-(UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView
dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc]
initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:CellIdentifier];
}
if(_imgList.count>0)
{
NSMutableString *fileName = [[NSMutableString alloc]
initWithString:[NSString stringWithFormat: #"img"]];
[fileName appendString: [_imgList objectAtIndex: _resultTag]];
UIImage *image = [UIImage imageNamed: fileName];
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
imageView.frame = CGRectMake(_xPos, 0, image.size.width, image.size.height);
[cell addSubview: imageView];
_xPos += SPACING;
}
return cell;
}
- (IBAction)btn:(UIButton *)sender {
[self.resultList beginUpdates];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:indexArray.count inSection:0];
[indexArray addObject:indexPath];
[self.resultList insertRowsAtIndexPaths:indexArray withRowAnimation:UITableViewRowAnimationFade];
[self.resultList endUpdates];
}
The issue is that you aren't changing the size of _imgList (from which, I assume, is derived the return value of the tableView:numberOfRowsInSection: method, but you don't show this method). By inserting a row in the table view, but not updating the data source to reflect that new row, you have created an inconsistency in your application.
To ensure that this doesn't happen, make the changes to your data source that reflect exactly the changes you are making to the rows, and do so before inserting or deleting any rows.
You don't show code that declares or instantiates _imgList so if it's not an NSMutableArray you'll need to make it one, and use methods like addObject:, insertObject:atIndex:, and removeObjectAtIndex: to insert and remove the appropriate objects. In your case, your code would become something like this:
NSInteger indexToAdd = _imgList.count;
[_imgList addObject:#"whatever"];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:indexToAdd
inSection:0];
[self.resultList insertRowsAtIndexPaths:#[indexPath]
withRowAnimation:UITableViewRowAnimationFade];
.h
int insertRow;
.m
- (void)viewDidLoad
{
self.insertRow = self.YourArr.count;
}
[self.tV beginUpdates];
NSIndexPath *iP = [NSIndexPath indexPathForRow:self.insertRow inSection:0];
[indexArray addObject:iP];
[self.tV insertRowsAtIndexPaths:indexArray withRowAnimation:UITableViewRowAnimationFade];
[self.tV endUpdates];
self.insertRow = self.insertRow + 1;
It may be solve your problem :)
I have given 2 colours to my cell.
cell.contentView.backgroundColor = indexPath.row % 2 ? [UIColor whiteColor] : [UIColor blackColor] ;
Now, when i delete a row, say i deleted the cell that contains the colour black, then there will be 2 rows that contains the colour white. So i tried refreshing the row;
[self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObjects:indexPath, nil] withRowAnimation:YES];
This works, but when i delete all the records i get the following exception; Why is this ?
Assertion failure in -[UITableView _endCellAnimationsWithContext:], /SourceCache/UIKit_Sim/UIKit-1448.89/UITableView.m:961 and
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 0. Attempt to delete more rows than exist in section.'
my code;
[self.tableView beginUpdates];
[self.myArray removeObjectsInArray:discardedItems ];
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObjects:indexPath, nil] withRowAnimation:YES];
[self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObjects:indexPath, nil] withRowAnimation:YES];
[self.tableView endUpdates];
How can i solve this ?
It thinks you have more rows than you do. Try something like this...
[self.tableView beginUpdates];
[self.myArray removeObjectsInArray:discardedItems ];
[self.tableView reloadData];
[self.tableView endUpdates];
Due to the nature of the tableview's data delegate, once you modify your data model the rest should follow.
Ok, let's think that you have 6 objects in your array, each one corresponding to the data-source for each cell. So, the delegate methods are called and you can actually edit the row and remove it, so you will end up now with 5 objects. What I always do and works like a charm is first re-sizing the array with the data-source and then updating the view.
//Remove the data
[self.myArray removeObjectsInArray:discardedItems ];
//Update the view
[self.tableView beginUpdates];
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObjects:indexPath, nil] withRowAnimation:YES];
[self.tableView endUpdates];
Unless you need to reload the data, meaning that some of the data-sources changed, you probably shouldn't be calling the update method. The exception was probably coming because you were trying to get info out of the sixth element of the array before you even finished updating the UITableView.
Hopefully this will work.
I want to reload only one section not the full table. Is there any method in UITableView.
[tableView reloadData] is used to load full table.
I want to know how to load only one section, as I have large number of rows in the table.
The reloadSections method bugs me -- as I have to construct a few objects. This is great if you need the flexibility, but sometimes I also just want the simplicity too. It goes like this:
NSRange range = NSMakeRange(0, 1);
NSIndexSet *section = [NSIndexSet indexSetWithIndexesInRange:range];
[self.tableView reloadSections:section withRowAnimation:UITableViewRowAnimationNone];
This will reload the first section. I prefer to have a category on UITableView and just call this method:
[self.tableView reloadSectionDU:0 withRowAnimation:UITableViewRowAnimationNone];
My category method looks like this:
#implementation UITableView (DUExtensions)
- (void) reloadSectionDU:(NSInteger)section withRowAnimation:(UITableViewRowAnimation)rowAnimation {
NSRange range = NSMakeRange(section, 1);
NSIndexSet *sectionToReload = [NSIndexSet indexSetWithIndexesInRange:range];
[self reloadSections:sectionToReload withRowAnimation:rowAnimation];
}
Yes, there is:
- (void)reloadSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation
But you can reload only sections which contain same number of rows (or you have to manually add or remove them). Otherwise you will get:
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 2. The number of rows contained in an existing section after the update (1) 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 (0 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).'
Which is not required when you use [tableView reloadData].
When you need to reload a section and you have changed number of rows inside it, you could use something like this:
NSIndexSet *indexSet = [NSIndexSet indexSetWithIndex:section];
[self beginUpdates];
[self deleteSections:indexSet withRowAnimation:rowAnimation];
[self insertSections:indexSet withRowAnimation:rowAnimation];
[self endUpdates];
If you put it in a category (like bandejapaisa shows) it could look like this:
- (void)reloadSection:(NSInteger)section withRowAnimation:(UITableViewRowAnimation)rowAnimation {
NSIndexSet *indexSet = [NSIndexSet indexSetWithIndex:section];
[self beginUpdates];
[self deleteSections:indexSet withRowAnimation:rowAnimation];
[self insertSections:indexSet withRowAnimation:rowAnimation];
[self endUpdates];
}
For Swift 3, 4 and 5
let sectionToReload = 1
let indexSet: IndexSet = [sectionToReload]
self.tableView.reloadSections(indexSet, with: .automatic)
that the correct way:
[self.tableView beginUpdates];
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationNone];
[self.tableView endUpdates];
Based on the accepted answer here, I made a function that will reload all sections in the table using an animation. This could probably be optimized by reloading only visible sections.
[self.tableView reloadData];
NSRange range = NSMakeRange(0, [self numberOfSectionsInTableView:self.tableView]);
NSIndexSet *sections = [NSIndexSet indexSetWithIndexesInRange:range];
[self.tableView reloadSections:sections withRowAnimation:UITableViewRowAnimationFade];
In my case, I had to force a reloadData before the section animation, because the underlying data for the table had changed. It animates properly however.
You need this... For Reload Row
- (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation
or For Reload section
- (void)reloadSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation
Here is the method, you can pass section details in different ways
[self.tableView reloadSections:[[NSIndexSet alloc] initWithIndex:1] withRowAnimation:NO];
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:1] withRowAnimation:UITableViewRowAnimationNone];
Reloading particular sections improves performance for the table view as well some time it also avoid some issues like floating/moving custom headers-footers in your view. SO try to use reloadSection than relaodData whenever possible
Try to use
[self.tableView beginUpdates];
[self.tableView endUpdates];
Hope this will solve your issue.
But you can reload only sections which contain same number of rows (or you have to manually add or remove them). Otherwise you will get an NSInternalInconsistencyException.
Steps:
calculate which rows to remove and/or insert
generate an IndexPath array from these
call related tableView methods
now you can safely call reloadSections :) Reload section will call update for the rest of the indexes.
Or you can use a library like : https://github.com/onmyway133/DeepDiff
Swift pseodo code:
tableView.deleteRows(at: valueIndexesToRemove, with: .automatic)
tableView.insertRows(at: valueIndexesToInsert, with: .automatic)
tableView.reloadSections(IndexSet([section]), with: .automatic)
If you have custom section view you can add a weak reference to it in your view controller and update it whenever you want. Here is my code for reference:
#property (weak, nonatomic) UILabel *tableHeaderLabel;
....
-(UIView *) tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
UITableViewHeaderFooterView *myHeader = [[UITableViewHeaderFooterView alloc] init];
UILabel *titleLabel = [[UILabel alloc] init];
[titleLabel setFrame:CGRectMake(20, 0, 280, 20)];
[titleLabel setTextAlignment:NSTextAlignmentRight];
[titleLabel setBackgroundColor:[UIColor clearColor]];
[titleLabel setFont:[UIFont systemFontOfSize:12]];
[myHeader addSubview:titleLabel];
self.tableHeaderLabel = titleLabel; //save reference so we can update the header later
return myHeader;
}
Then later on you can update your section like this:
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
self.tableHeaderLabel.text = [NSString stringWithFormat:#"Showing row: %ld", indexPath.row];
}
I have a couple of NSMutableArrays which i need to clear when refreshing the view. However, when I try to clear them with [array removeAllObjects]; my tableview crashes due to index beyond bounds error. All i do with the refresh, is clear the arrays and call the same function as in viewDidLoad for filling the tableview. [tableView reloadData] doesn't get called until the very last line of the method.
EDIT: It's highly likely that the issue is this: I use a pull to refresh external lib, and when you scroll up and release the table, it bounces back down and thus the UITableView tries to load the next cell, which it cant because the array is cleared, and it's still being loaded.
Answer: removeAllObjects from the arrays, immediately do a self.tableView reloadData and then continue with the rest.
problem might be due to numberOfRowsInSection returning some count and your data source array is empty.
just call [array removeAllObjects] and in numberOfRowsInSection return [array count].
I hope it will resolve your issue. Best of Luck!!!
I delete cells from my table view in the following manner-
NSMutableArray* indexPathsToDelete = [[NSMutableArray alloc] init];
for(unsigned int i = 0; i < [self.array count]; i++)
{
[indexPathsToDelete addObject:[NSIndexPath indexPathForRow:i inSection:0]];
}
[self.tableView beginUpdates];
[self.array removeAllObjects];
[self.tableView deleteRowsAtIndexPaths:indexPathsToDelete withRowAnimation:UITableViewRowAnimationLeft];
[self.tableView endUpdates];
[indexPathsToDelete release];
When you refresh your array, first check if it has the object or not & then reinitialize your array and release one.
What i did was
[array removeAllObjects];
then call
[self.tableview reloadData];
I am getting this error while I am trying to load the data into my table view.
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (73) must be equal to the number of rows contained in that section before the update (71), plus or minus the number of rows inserted or deleted from that section (3 inserted, 0 deleted).
What could be wrong?
Thanks
EDIT :
I am initializing the array on ViewWillAppear and adding new objects to the same array on Tableview's didSelectRowAtIndexPath method
Here is the code On viewWillAppear :
cellTextArray = [[NSMutableArray alloc] init];
[cellTextArray addObjectsFromArray:newPosts];
Here is the code which modifies the array on didSelectRowAtIndexPath :
[cellTextArray addObjectsFromArray:newPosts];
NSMutableArray *insertIndexPaths = [NSMutableArray array];
for (NSUInteger item = count; item < count + newCount; item++) {
[insertIndexPaths addObject:[NSIndexPath indexPathForRow:item
inSection:0]];
}
[self.table beginUpdates];
[self.table insertRowsAtIndexPaths:insertIndexPaths
withRowAnimation:UITableViewRowAnimationFade];
[self.table endUpdates];
[self.table scrollToRowAtIndexPath:indexPath
atScrollPosition:UITableViewScrollPositionNone
animated:YES];
NSIndexPath *selected = [self.table indexPathForSelectedRow];
if (selected) {
[self.table deselectRowAtIndexPath:selected animated:YES];
}
Here newPosts is an array which has the values that are added to cellTextArray on
didSelectRowAtIndexPath method and viewWillAppear method.
If you have updated the array of data after initialization then you can call this method [yourTable reloadData].
And, it be better if you post the codes here. Then may be some one can help you quickly.
I think, the problem is with the statement in the didSelectRowAtIndexPath method.
You have added the following statement
[self.table insertRowsAtIndexPaths:insertIndexPaths withRowAnimation:UITableViewRowAnimationFade];
This statement adds the new rows to the table. but the number of rows in the datasource array for that section is different with the number of rows in that section of that table.
So the App is terminated.
Please try by removing the above statement and add required data to the dataSource Array at required indexes and reload the table
Regards,
Satya