Reputation: 749
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 DetailViewController
and 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];
}
Upvotes: 2
Views: 1479
Reputation: 119031
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.
Upvotes: 1
Reputation: 12687
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.
Upvotes: 0