Inserting new item in UITableView leads to invalid number of sections - objective-c

I'm losing my mind here. Relevant code:
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
return [[self.currentWorkout movements] count];
}
And...
- (void)addMovement:(Movement *)movement{
[self.currentWorkout.movements insertObject:movement atIndex:0];
[self.table beginUpdates];
NSIndexPath *path = [NSIndexPath indexPathForRow:0 inSection:0];
[self.table insertRowsAtIndexPaths:#[path] withRowAnimation:UITableViewRowAnimationTop];
[self.table endUpdates];
}
I have confirmed that insertObject:atIndex: is adding the object; the count of currentWorkout increases by one.
Error: 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of sections. The number of sections contained in the table view after the update (8) must be equal to the number of sections contained in the table view before the update (7), plus or minus the number of sections inserted or deleted (0 inserted, 0 deleted).'

Solved: using wrong method to insert sections.
- (void)addMovement:(Movement *)movement{
[self.currentWorkout.movements insertObject:movement atIndex:0];
NSLog(#"%d",(int)[self numberOfSectionsInTableView:self.table]);
[self.table beginUpdates];
[self.table insertSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationTop];
[self.table endUpdates];
}
Removing the [self.table beginUpdates]; method call is important because otherwise the table will think it has the number of rows it would get after adding the object before that object is actually added.

Related

Remove Row from UITableView

Please read before marking as duplicate....
I am currently trying to remove rows from a UITableView but without any result.
I have a delete button on the cell of the UITableView. Once selected i hope to remove that row.
When the delete button is selected it calls the following method
- (void)didClickOnCellAtIndex:(NSInteger)cellIndex withData:(id)data
{
[self.mainTableView beginUpdates];
NSIndexPath *path = [NSIndexPath indexPathForItem:cellIndex inSection:0];
//
[self.mainTableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:path] withRowAnimation:UITableViewRowAnimationFade];
[filteredSubArrayForExpandableSection removeObjectAtIndex:cellIndex];
[appDelegate.managedObjectContext deleteObject:[self.fetchedResultsController objectAtIndexPath:path]];
[appDelegate.managedObjectContext save:nil];
[self.mainTableView endUpdates];
//
// Save the context.
NSError *error;
if (![appDelegate.managedObjectContext save:&error]) {
/*
Replace this implementation with code to handle the error appropriately.
abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. If it is not possible to recover from the error, display an alert panel that instructs the user to quit the application by pressing the Home button.
*/
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
// abort();
}
}
The above code will return the following issue
Reason: Invalid update: invalid number of rows in section 0. The
number of rows contained in an existing section after the update (8)
must be equal to the number of rows contained in that section before
the update (8), plus or minus the number of rows inserted or deleted
from that section (0 inserted, 1 deleted) and plus or minus the number
of rows moved into or out of that section (0 moved in, 0 moved out)
When i reload the app the row still remains.
Ok so the number of rows in this case should be 7!
So logically I added the following code in between the beginUpdates and endUpdates method.
numberOfRows = numberOfRows - 1;
This however returns the following error
-[ViewController deleteRow:]: unrecognized selector sent to instance 0x7a278a00 (lldb)
I can load up the app and the row has been deleted as it should but obviously the app crashed out prior to this.
if i remove the deleteRowsAtIndexPaths method from the didClick method and use the following NSFetchedController Delegate method i get the exact same above issues
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {
switch (type) {
case NSFetchedResultsChangeInsert: {
[self.mainTableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
}
case NSFetchedResultsChangeDelete: {
[self.mainTableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
}
case NSFetchedResultsChangeUpdate: {
//[self configureCell:(TSPToDoCell *)[self.tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
NSLog(#"results changed update called");
break;
}
case NSFetchedResultsChangeMove: {
[self.mainTableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
[self.mainTableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
}
Update
number of rows method
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if (canExpand) {
//if can expand section becomes top row for expansion effect thus we need to add 1 to show ALL rows
return numberOfRows + 1;
}
else
{
//if cannot expand section we remove 1 from total
return numberOfRows -1 ;
}
return 0 ;
}
any help regarding this will be greatly received
Thanks

UITableView insert row below tapped cell

I'm extremely confused. I have looked at several posts to try to figuere out how to do this. What i would like to do is detect when a UITableViewCell is tapped and then insert a row right below that row with a menu.
I implemented detecting the tapped item but I can't figure out how to insert the new row right below the row that was just tapped.
Here is the method that handles the tapped cell.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
indexPath = nil;
[tableView deselectRowAtIndexPath:indexPath animated:YES];
Locations *location = nil;
if (self.searchDisplayController.active) {
indexPath = [self.searchDisplayController.searchResultsTableView indexPathForSelectedRow];
location= [self.searchResults objectAtIndex:indexPath.row];
NSLog(#"location : %#" ,location.locationName);
[self.locations addObject:#"New Entry"];
[tableView insertRowsAtIndexPaths:#[indexPath] withRowAnimation:YES];
//[self.tableViewForScreen insertRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
} else {
indexPath = [self.tableView indexPathForSelectedRow];
location = [self.locations objectAtIndex:indexPath.row];
NSLog(#"location : %#" ,location.locationName);
[tableView insertRowsAtIndexPaths:#[indexPath] withRowAnimation:YES];
}
[tableView deselectRowAtIndexPath:indexPath animated:YES];
// set the tapped item pointer equals to the locations mutable array variable wich essentially is the
// data source of the table view.
//Locations *tappedItem =[self.locations objectAtIndex:indexPath.row];
//access the location name and print to log.
}
Obviously it fails at the part of insert
[tableView insertRowsAtIndexPaths:#[indexPath] withRowAnimation:YES];
Can someone give me a hand?
The error is the following.
reason: 'Invalid update: invalid number of rows in section 0. The
number of rows contained in an existing section after the update (154)
must be equal to the number of rows contained in that section before
the update (154), 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).'
Here's a post that I looked at.
UITableView action row
http://adcdownload.apple.com//videos/wwdc_2011__sd/session_125__uitableview_changes_tips_tricks.m4v
The problems is that you call insertRowsAtIndexPath: but you don't first update the data source to include the data for the new row.
You seem to do this correctly when selecting a row in the search table but not the main table.
First of all do not use
indexPath = [self.searchDisplayController.searchResultsTableView indexPathForSelectedRow];
you can take indexPath from method parameters
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
It seems that you have problems because you are calling insertRowsAtIndexPaths but you are not updating data source.

Updating UITableView that is managed by NSFetchedResultsController and has 1 extra cell in the first section

Master Detail Application -
I have a UITableViewController that is managed by an NSFetchedResultsController and its delegate methods. I also have one extra cell in the first section of the tableview that has a UIWebView in it that displays an embedded video. This cell is not part of the NSFetchedResultsController. I add it inside of an IF statement when -tableView cellForRowAtIndexPath is called, that checks to see if it's the first section and first row. Everything works great for the most part. I can select a row and display it in the DetailViewController, I can delete items from the tableView, I can change the information in the DetailViewControllerand the tableViewCell labels update with out any problem.
MY ISSUE
The only time I have an issue with the way it is setup is, when I delete the last object from the first section in the tableView.
THE ERROR
CoreData: error: Serious application error. An exception was caught
from the delegate of NSFetchedResultsController during a call to
-controllerDidChangeContent:. Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section
after the update (3) must be equal to the number of rows contained in
that section before the update (2), 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). with userInfo (null)
CODE
numberOfRowsInSection
This is where I check to see if the video is available and if the section is 0 return 1 extra row. else return the object count from the NSFetchedResultsController. I'm assuming this is where my issue is. However, I don't know how to fix it. Because I have it managed by the NSFetchedResultsController delegate methods is there any thing I can do inside of the "type" in the switch statement?
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if (section == 0 && self.videoInfoReceived == YES) {
id <NSFetchedResultsSectionInfo> secInfo = [[self.fetchedResultsController sections] objectAtIndex:section];
return [secInfo numberOfObjects] + 1;
} else {
id <NSFetchedResultsSectionInfo> secInfo = [[self.fetchedResultsController sections] objectAtIndex:section];
return [secInfo numberOfObjects];
}
}
NSFetchedResultsController Delegates
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
{
[self.tableView beginUpdates];
}
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type
{
switch(type) {
case NSFetchedResultsChangeInsert:
[self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
NSLog(#"didChangeSection - ChangeInsert");
break;
case NSFetchedResultsChangeDelete:
[self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
NSLog(#"didChangeSection - ChangeDelete");
break;
}
}
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject
atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type
newIndexPath:(NSIndexPath *)newIndexPath
{
UITableView *tableView = self.tableView;
switch(type) {
case NSFetchedResultsChangeInsert:
[tableView insertRowsAtIndexPaths:#[newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
NSLog(#"didChangeObject - ChangeInsert");
break;
case NSFetchedResultsChangeDelete:
[tableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationFade];
NSLog(#"didChangeObject - ChangeDelete");
break;
case NSFetchedResultsChangeUpdate:
[self configureCell:[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
NSLog(#"didChangeObject - ChangeUpdate");
break;
case NSFetchedResultsChangeMove:
[tableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationFade];
[tableView insertRowsAtIndexPaths:#[newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
NSLog(#"didChangeObject - ChangeMove");
break;
}
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
[self.tableView endUpdates];
}
You need more conditional code to check if any index path you're sent is for the first (zero) section. If it is, the FRC is being given the 'wrong' row number because you have told the table view one row count and the FRC understands a different one.
Basically, everywhere you receive an index path from the table view, check the section, if it == 0, create a new index path with the same section and row - 1 and use that to access the objects in the FRC.
Conversely, when you receive an index path from the FRC you need row + 1 before you can use it to ask the table view to insert/delete/move.
In your question you state:
when I delete the last object from the first section in the tableView
Which would lead me to expect the exception to state the following (emphasis added):
CoreData: error: Serious application error. An exception was caught from the delegate of NSFetchedResultsController during a call to -controllerDidChangeContent:. Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (0) 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 (0 inserted, 1 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out). with userInfo (null)
Because the exception states that the row count is increasing from 2 to 3 without any rows being added, I think that your error is down in the model layer and not in the table view code. Your table view code looks great to me.

Can't programmatically delete row from UITableView

I'm trying to implement a custom deletion process for my app, as my costumer doesn'y want the red circle provided by table view edition mode. I have added a deletion button for every row, with row number in its tag property. Once user clicks deletion button, it fires following method:
-(IBAction)deleteRow:(id)sender{
UIButton *tempButton = (UIButton*)sender;
[self updateTotals];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:tempButton.tag inSection:0];
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
[tableView reloadData];
[sharedCompra removeItem:tempButton.tag];
tempButton=nil;
}
And I allways get this 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 (3) must be equal to the number of rows contained in that section before the update (3), plus or minus the number of rows inserted or deleted from that section (0 inserted, 1 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).'
So I don't know if I'm missing something in this piece of code.
Many thanks.
You're trying to delete a row whereas your data source still reflects the original state (i. e. that before the deletion). You have to update the data source first, it's only then that you can issue a deletion from the table viev. You also don't need to set the button to nil, nor do you need to call - reloadData on the table view:
- (void)deleteRow:(id)sender
{
UIButton *tempButton = sender;
[self updateTotals];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:tempButton.tag inSection:0];
[sharedCompra removeItem:tempButton.tag];
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
}
(One thing you should do, however, is paying attention to how you format your code.)
Call [sharedCompra removeItem:tempButton.tag]; before [tableView deleteRowsAtIndexPaths... and [tableView reloadData]
The problem is that when you call deleteRowsAtIndexPath: it is calling numberOfRowsInSection which returns the same count from your model.
Also reloadData call is not needed here.
When deleting and adding rows, you need to call beginUpdates, here how it should look.
[tableView beginUpdates];
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
[tableView endUpdates];
and you don't need to call reloadData, or it will cancel the animation of the deleteRows, and insertRows methods. But you do need to reset the data, so if you're using an NSMutableArray, you need to remove the object first, so item at index 0, then you can delete row 0 in table. the number of rows needs to match the number of objects in that array when it ends deleting or it will also crash.

Deleting all table rows in a section

For my iPad project I need to be able to reload table rows.
This is how it should works, depend on some "id" selected from a popupview, i need to be able to reload table rows, table rows are dynamics and has a UITextField in it. I tried to use [self.tableView reload] but for some reason it does not update the rows correctly. Something like UITextField placeholder owned by previous "id" does not change. What I have in mind is to remove all cells in that specific section and reload the table with the new "id". When i do this i got this exception:
* 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 (3) must be equal to the number of rows contained in that section before the update (3), plus or minus the number of rows inserted or deleted from that section (0 inserted, 3 deleted).'
The project code:
- (void)selectedArchiefID:(NSString *) value {
[self.popOverController dismissPopoverAnimated:YES];
// collects all available document indexfields
int rowCount = [indexDefinities count];
NSMutableArray *indexPaths = [[NSMutableArray alloc] init];
for (int curIndex=0; curIndex < rowCount; curIndex++) {
[indexPaths addObject:[NSIndexPath indexPathForRow:curIndex inSection:0]];
}
// deletes rows
if ([indexPaths count] > 0) {
[self.tableView beginUpdates];
[self.tableView deleteRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationNone];
//[self.tableView deleteSections:0 withRowAnimation:UITableViewRowAnimationNone];
[self.tableView endUpdates]; //crashes here
}
[self.tableView reloadData];
}
Any idea what i did wrong?
Looks like you forgot to change the data of the datasource of the tableview.
You can't just change the tableview layout.
If you want to remove a section, you have to remove this section from the datasource first.
You could do this the easy way, by removing the section in a NSMutableArray (which is your datasource).
Or you could write a big if then else monster that figures out how much sections (and rows) to return in numberOfSectionsInTableView: and tableView:numberOfRowsInSection:.
Doesn't matter how you do it, but you have to remove the data before you update the tableview.
One more thing, having both [self.tableView beginUpdates]; /*...*/ [self.tableView endUpdates]; and [self.tableView reloadData]; in one method is usually useless. THe latter would kill the animation effect you get from the first one.