How do I insert new cells in a UITableView? - iphone

I am trying to add a row to a tableview using
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
[self.tableView insertRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
It throws an exception where I call insertRowsAtIndexPaths:withRowAnimation:, which says
Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayI objectAtIndex:]: index 1 beyond bounds [0 .. 0]'
It is returning the correct number of rows and sections, but it never makes it to cellForRowAtIndexPath before throwing the exception. I am doing this with a storyboard also and I have the tableView set to have static cells because the majority of the tableView does in fact have static cells. I really don't want to switch over to dynamic prototypes because then I can't have IBOutlets and IBActions, which I am using, and I would have to essentially start over with respect to the layout of the cells. Is there any way for me to get rid of this exception without changing my tableView to have dynamic prototype cells?
Edit
The line right before what I posted is
[itemsInCurrentPurchase insertObject:itemName atIndex:0];
which is supposed to update the array. I am using
[itemsInCurrentPurchase count]
for my number of rows in section and it is returning the correct number after the update. The problem is that the cellForRowAtIndexPath method is not even being reached. During the update is there anything else that gets called between the number of rows and the cell type that could be causing this error?

You need to update your data source with this new object before inserting the row. Otherwise it will crash while trying to update the table view with this new row.
for eg:-
[self.dataSourceArray insertObject:object atIndex:indexPath.row];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
[self.tableView insertRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];

Related

Delete UITableViewCell using custom UIButton

I have encountered this error when trying to delete a cell, using a custom button.
* Terminating app due to uncaught exception 'NSRangeException', reason: '* -[__NSArrayM removeObjectAtIndex:]: index 1 beyond bounds [0 .. 0]'
Heres how my code is structured. I have a custom UITableViewCell that contains a UIButton which has an action that deletes the cell. The action for deleting the cell is in my main View controller, that contains the actual indexPath for the cells. This structure works fine.
Heres the action to delete the cell.
NSIndexPath *indexPath = [self globalIndexPath];
[self.array removeObjectAtIndex:[indexPath row]];
[db deleteTaskAtIndex:[indexPath row]];
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObjects:indexPath,nil] withRowAnimation:UITableViewRowAnimationFade];
When I declare the indexPath above, the globalIndexPath property is set in my cellForRowAtIndexPath, giving it the value of the original indexPath. This is how I declared it.
[self setGlobalIndexPath:indexPath];
Now I have laid some NSLogs here an there to log the indexPath. For example in the viewWillAppear method and the viewDidLoad method, and both give me the accurate indexPath, and I even checked the array outputs and all of them return accurate results, so I really don't know why it's giving me the error.
Here is the code in my custom cell, to detect when the button is tapped.
NSArray *notificationArray = [[UIApplication sharedApplication] scheduledLocalNotifications];
if (!notificationArray || !notificationArray.count)
{
[[NSNotificationCenter defaultCenter] postNotificationName:#"DeleteSignal" object:nil];
}
else
{
UILocalNotification *notif = [notificationArray objectAtIndex:[checkbox tag]];
[[UIApplication sharedApplication] cancelLocalNotification:notif];
}
And then I delete the the cell using the NSNotificationCenter with the key DeleteSignal.
If you are setting the globalIndexPath in cellForRowAtIndexPath (and nowhere else), globalIndexpath will always know the indexPath for the last cell, which got created or dequeued, not the one where the user might actually use the button. Seems already broken by design. Or are your cells that large that only one is visible?
At first I would try setting the tag of your UIButton of your cell in cellForRowAtIndexPath
myButton.tag = indexPath.row + SOMEOFFSETTOASSUREYOURTAGDOESNTGET0;
and then get the indexPath in your action via this tag:
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:(UIButton*)sender.tag - OFFSET inSection:0];
Of course that only works if you have just one section. If you need to handle multiple sections you could use an array which contains all your indexPaths and set the tag of your UIButton to the index of that indexPath in your array.
UPDATE
While it's technically possible to (mis)use NSNotificationCenter for what you are doing, I would really advise you to walk through this tutorial.

exception while deleting a cell

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.

TableView app terminated due to 'NSInternalInconsistencyException'

I'm trying to get the hang of UITableViews and everything that goes with it. At the moment I have the following code:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 10;
}
-(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] autorelease];
}
[cell.textLabel setText:[NSString stringWithFormat:#"I am cell %d", indexPath.row]];
return cell;
}
- (IBAction)killItem {
NSIndexPath *indexToDelete = [NSIndexPath indexPathForRow:2 inSection:0];
[tbl deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexToDelete] withRowAnimation:UITableViewRowAnimationRight];
}
And I get the following error when initiating the "killItem" function:
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 (10) must be equal to the number of rows contained in that section before the update (10), plus or minus the number of rows inserted or deleted from that section (0 inserted, 1 deleted).'
The way I understand it tableViews basically have a delegate and a data source where the data source, among other things, determines how many rows should be in the tableView. Through some searches here at stackoverflow I've found that this error is caused when the "data source doesn't match reality", when it's searching for rows that don't exist, that I have deleted.
I might have gotten this wrong but that's what I think is doing it. So my question is, how do I get these to match so that I can avoid this error?
For reference, I've looked in to the following posts without understanding what I need to do:
Error : Number of Rows In Section in UITableView in iPhone SDK
Slide UITableViewCell
Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[NSMutableArray objectAtIndex:]: index 1 beyond bounds [0 .. 0]'
Adding [tbl reloadData], [tbl beginUpdate] ... [tbl endUpdate] in the killItem function, doesen't seem to help my problem eighter.
Thank you in advance,
Tobias Tovedal
Tobias, what you need to do when deleting rows is
// tell the table view you're going to make an update
[tableView beginUpdates];
// update the data object that is supplying data for this table
// ( the object used by tableView:numberOfRowsInSection: )
[dataArray removeObjectAtIndex:indexPath.row];
// tell the table view to delete the row
[tableView deleteRowsAtIndexPaths:indexPath
withRowAnimation:UITableViewRowAnimationRight];
// tell the table view that you're done
[tableView endUpdates];
When you call endUpdate the number returned from tableView:numberOfRowsInSection: must be the same as the number at beginUpdate minus the number of rows deleted.
It's pretty easy, the problem lies here:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 10;
}
The delegate pattern used by apple, means that you're the one responsible on managing the content of the UITableView through its delegates, meaning that, if you delete a row, you're also responsible of deleting the data from the data model.
So, after deleting a row, it would make sense that the number of rows in section would decrease to "9", yet, your function is always returning 10, and thus throwing the exception.
Typically, when using an table, and the contents will change, an NSMutableArray is pretty common, you do something like this:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [arrayWithStuff count];
}
And then, deleting an object (removeObjectAtIndex:) from the array would automatically update the number of rows.
(Edit: Replied at about the same time as Mike Hay, try following his advice too! I skipped the begin/end Update, because it seems you already read about it)

Error : Number of Rows In Section in UITableView in iPhone SDK

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

reloadRowsAtIndexPaths:withRowAnimation: crashes my app

I got a strange problem with my UITableView: I use reloadRowsAtIndexPaths:withRowAnimation: to reload some specific rows, but the app crashes with an seemingly unrelated exception: NSInternalInconsistencyException - Attempt to delete more rows than exist in section.
My code looks like follows:
[self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:0 inSection:0]] withRowAnimation:UITableViewRowAnimationFade];
When I replace that reloadRowsAtIndexPaths:withRowAnimation: message with a simple reloadData, it works perfectly.
Any ideas?
The problem is that you probably changed the number of items of your UITableView's data source. For example, you have added or removed some elements from/to the array or dictionary used in your implementation of the UITableViewDataSource protocol.
In that case, when you call reloadData, your UITableView is completely reloaded including the number of sections and the number of rows.
But when you call reloadRowsAtIndexPaths:withRowAnimation: these parameters are not reloaded. That causes the next problem: when you are trying to reload some cell, the UITableView checks the size of the datasource and sees that it has been changed. That results in a crash. This method can be used only when you want to reload the content view of the cell (for example, label has changed or you want to change its size).
Now if you want to remove/add cells from/to a UITableView you should use next approach:
Inform the UITableView that its size will be changed by calling method beginUpdates.
Inform about inserting new row(s) using method - (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation.
Inform about removing row(s) using method - (void)deleteRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation.
Inform the UITableView that its size has been changed by calling the method endUpdates.
I think the following code might work:
[self.tableView beginUpdates];
[self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:0 inSection:0]] withRowAnimation:UITableViewRowAnimationFade];
[self.tableView endUpdates];
I had this problem which was being caused by a block calling reloadRowsAtIndexPaths:withRowAnimation: and a parallel thread calling reloadData.
The crash was due to reloadRowsAtIndexPaths:withRowAnimation finding an empty table even though I'd sanity checked numberOfRowsInSection & numberOfSections.
I took the attitude that I don't really care if it causes an exception. A visual corruption I could live with as a user of the App than have the whole app crash out.
Here's my solution to this which I'm happy to share and would welcome constructive criticism. If there's a better solution I'm keen to hear it?
- (void) safeCellUpdate: (NSUInteger) section withRow : (NSUInteger) row {
// It's important to invoke reloadRowsAtIndexPaths implementation on main thread, as it wont work on non-UI thread
dispatch_async(dispatch_get_main_queue(), ^{
NSUInteger lastSection = [self.tableView numberOfSections];
if (lastSection == 0) {
return;
}
lastSection -= 1;
if (section > lastSection) {
return;
}
NSUInteger lastRowNumber = [self.tableView numberOfRowsInSection:section];
if (lastRowNumber == 0) {
return;
}
lastRowNumber -= 1;
if (row > lastRowNumber) {
return;
}
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:row inSection:section];
#try {
if ([[self.tableView indexPathsForVisibleRows] indexOfObject:indexPath] == NSNotFound) {
// Cells not visible can be ignored
return;
}
[self.tableView reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationNone];
}
#catch ( NSException *e ) {
// Don't really care if it doesn't work.
// It's just to refresh the view and if an exception occurs it's most likely that that is what's happening in parallel.
// Nothing needs done
return;
}
});
}
After many try, I found "reloadRowsAtIndexPaths" can be only used in certain places if only change the cell content not insert or delete cells. Not any place can use it, even you wrap it in
[self beginUpdates];
//reloadRowsAtIndexPaths
[self endUpdates];
The places I found that can use it are:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
- (IBAction) unwindToMealList: (UIStoryboardSegue *) sender
Any try from other places like call it from "viewDidLoad" or "viewDidAppear", either will not take effect (For the cell already loaded I mean, reload will not take effect) or cause exception.
So try to use "reloadRowsAtIndexPaths" only in those places.
You should check cell visibility before reload. Here is Swift 3 code:
let indexPath = IndexPath(row: offset, section: 0)
let isVisible = tableView.indexPathsForVisibleRows?.contains{$0 == indexPath}
if let v = isVisible, v == true {
tableView.reloadRows(at: [indexPath], with: .automatic)
}
I had the same issue. In my case; it was happening only if another view controller pop/pushed over existing table view controller and then[self.tableView reloadRowsAtIndexPaths] function is called.
reloadRowsAtIndexPaths call was hiding/showing different rows in a table view which is having over 30, visually complex, rows. As i try to fix the issue i found that if i slightly scroll the table view app wasn't crashing. Also it wasn't crashing if i don't hide a cell (by returning 0 as height)
To resolve the issue, i simply changed the "(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath" function and returned at least 0.01 as row height.
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
....
return rowModel.height + 0.01; // Add 0.01 to work around the crash issue.
}
it solved the issue for me.
THIS IS OLD. DO NOT USE.
I just bumped into this issue when I was calling reloadRowsAtIndexPaths... in order to change the cell to an editing cell containing a UITextField. The error told me I was deleting all of the rows in the table. To solve the problem, I removed:
[self.tableView beginUpdates];
NSArray *reloadIndexPath = [NSArray arrayWithObject:[NSIndexPath indexPathForRow:count inSection:section]];
[self.tableView reloadRowsAtIndexPaths:reloadIndexPath withRowAnimation:UITableViewRowAnimationFade];
[self.tableView endUpdates];
and replaced it with
[self.tableView reloadData];
The app crashes because you have made some changes to your tableView. Either you have added or deleted some rows to the tableView. Hence when the view controller asks your model controller class for data, there is a mismatch in the indexPaths. Since the indexPaths have changed after modification.
So either you simply remove the call
[self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:0 inSection:0]] withRowAnimation:UITableViewRowAnimationFade];
or replace it with
[self.tableView reloadData];
Calling reloadData checks your number of sections, number of rows in each section and then reloads the whole thing.
If data count changes completely, then use reloadData else, there is three functions to do it.
When data count changes we use insertRows / deleteRows and when data count still the same use reloadRows.
Important! don't forget call beginUpdates and endUpdates between insertRows/deleteRows/reloadRows calls.