I am working upon a Core-data app which involves collapsable sections.
Basically, if I move a managed object across section, or just delete it when its section is opened, I get the following error:
2014-04-28 19:38:44.690 uRSS[663:60b] 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 7. 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 (3), 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)
I believe its about the way I handle changes in the didChange Object
method but I am stuck ATM:
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject
atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type
newIndexPath:(NSIndexPath *)newIndexPath
{
UITableView *tableView = self.tableView;
switch(type) {
case NSFetchedResultsChangeInsert:
NSLog(@"MasterViewController::didChangeObject **** Inserting something in %@**** ", _detailViewController.detailItem.category.name);
if ([_detailViewController.detailItem.category.open boolValue]) {
[tableView insertRowsAtIndexPaths:@[newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
}
break;
case NSFetchedResultsChangeDelete:
NSLog(@"MasterViewController::didChangeObject **** Deleting something in %@**** ", _detailViewController.detailCategory.name);
if ([_detailViewController.detailCategory.open boolValue]) {
[tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade];
}
break;
case NSFetchedResultsChangeUpdate:
NSLog(@"MasterViewController::didChangeObject **** Changing something in %@**** ", _detailViewController.detailCategory.name);
if ([_detailViewController.detailCategory.open boolValue]) {
[self configureCell:[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
}
break;
case NSFetchedResultsChangeMove:
NSLog(@"MasterViewController::didChangeObject **** Moving something from %@ to %@**** ", _detailViewController.detailCategory.name, _detailViewController.detailItem.category.name);
if ([_detailViewController.detailCategory.open boolValue]) {
[tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade];
}
if ([_detailViewController.detailItem.category.open boolValue]) {
[tableView insertRowsAtIndexPaths:@[newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
}
break;
}
}
My detailView has 2 objects attached to it: detailItem
, a feed, and detailCategory
, the original Category, should it change.
Category has a name
and an open
property. If open is false, the Category (section) is collapsed.
So far I cannot delete objects or change their category without a crash. If the category is closed, I can delete them.
Whenever creating a new object (which has its own "new" category), it wrongly appears in the currently opened category. If no category is opened, then it will expectedly crete the new category with the new object.
Can somebody tell me how to handle this, please?
UPDATE(1): Here's some more from the UIDataSource:
This is when and how I move a Feed
across Categories
in my detailView:
-(void) updateCategory:(id)sender
{
if (!_categoriesPopover) {
return;
}
[_categoriesPopover dismissPopoverAnimated:YES];
[_mainMOC performBlockAndWait:^{
Category *category=_categoryView.categoryChosen;
if (!category) {
return;
}
self.detailItem.category.open=[NSNumber numberWithBool:NO];
self.detailCategory = self.detailItem.category;
self.detailItem.category=category;
self.detailItem.category.open=[NSNumber numberWithBool:YES];
NSError *error;
if (![_mainMOC save:&error])
{
NSLog(@"DetailViewController::updateCategory Error saving context: %@", error);
} else {
[[NSNotificationCenter defaultCenter] postNotificationName:@"feedUpdatedInDetailView" object:nil];
}
}];
}
And it happens once I have clicked on the chosen destination Category
in this screenshot:
UPDATE(2):
Here's the numberOfRowsInSection
method:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if (tableView == self.searchDisplayController.searchResultsTableView) {
return [_filteredCategoryArray count];
} else {
if ([[[self.fetchedResultsController sections][section] objects] count]>0) {
Category *cat = ((Feed *)[[[self.fetchedResultsController sections][section] objects] objectAtIndex:0]).category;
return [cat.open boolValue] ? [[self.fetchedResultsController sections][section] numberOfObjects] : 0;
} else {
return 0;
}
}
}
UPDATE(3):
I NSLogged some variables in order to see what was happening and I think we can forget the open/close aspect here.
a) Here's what happens in the DetailView:
-(void) updateCategory:(id)sender
{
if (!_categoriesPopover) {
return;
}
[_categoriesPopover dismissPopoverAnimated:YES];
Category *category=_categoryView.categoryChosen;
if (!category) {
return;
}
[_mainMOC performBlockAndWait:^{
NSLog(@"BEFORE: oldcat: %@ (%d) / newcat: %@ (%d)", _detailItem.category.name, [_detailItem.category.feeds count], category.name, [category.feeds count]);
self.detailItem.category.open=[NSNumber numberWithBool:NO];
self.detailCategory = self.detailItem.category;
self.detailItem.category=category;
self.detailItem.category.open=[NSNumber numberWithBool:YES];
NSError *error;
if (![_mainMOC save:&error])
{
NSLog(@"DetailViewController::updateCategory Error saving context: %@", error);
} else {
[[NSNotificationCenter defaultCenter] postNotificationName:@"feedUpdatedInDetailView" object:nil];
}
NSLog(@"AFTER: oldcat: %@ (%d) / newcat: %@ (%d)", _detailCategory.name, [_detailCategory.feeds count], _detailItem.category.name, [_detailItem.category.feeds count]);
}];
}
b) Here's what's in the MasterView:
case NSFetchedResultsChangeMove:
NSLog(@"MVC: oldcat: %@ (%d) / newcat: %@ (%d)", _detailViewController.detailCategory.name, [_detailViewController.detailCategory.feeds count], _detailViewController.detailItem.category.name, [_detailViewController.detailItem.category.feeds count]);
NSLog(@"MasterViewController::didChangeObject **** Moving something from %@ to %@**** ", _detailViewController.detailCategory.name, _detailViewController.detailItem.category.name);
[tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade];
[tableView insertRowsAtIndexPaths:@[newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
c) Here's what the log says:
2014-04-30 18:52:04.771 uRSS[465:60b] BEFORE: oldcat: trucs (2) / newcat: Misc (7)
2014-04-30 18:52:04.771 uRSS[465:60b] MVC: oldcat: trucs (1) / newcat: Misc (8)
2014-04-30 18:52:04.772 uRSS[465:60b] MasterViewController::didChangeObject **** Moving something from trucs to Misc****
2014-04-30 18:52:04.772 uRSS[465:60b] *** Assertion failure in -[UITableView _endCellAnimationsWithContext:], /SourceCache/UIKit_Sim/UIKit-2935.137/UITableView.m:1368
2014-04-30 18:52:04.772 uRSS[465:60b] 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 5. 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 (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). with userInfo (null)
2014-04-30 18:52:04.773 uRSS[465:60b] [AppDelegate::saveContextChanges] merged.
2014-04-30 18:52:04.774 uRSS[465:8c03] [AppDelegate::saveContextChanges] merged.
2014-04-30 18:52:04.775 uRSS[465:60b] AFTER: oldcat: trucs (1) / newcat: Misc (8)
What I don't get is that the DVC works in a PerformBlockAndWait loop but the MVC gets called with the new values before they seem to be committed on the DVC. How is that possible?
What's wrong???
UPDATE(4): I did as Marcus suggested and removed the MOC save. The logs look better (BEFORE and AFTER happen in the DVC, MVC happens expectedly after in the MVC and the values are correct):
2014-05-01 07:27:39.391 uRSS[742:60b] BEFORE: oldcat: Misc (8) / newcat: trucs (1)
2014-05-01 07:27:39.392 uRSS[742:60b] AFTER: oldcat: Misc (7) / newcat: trucs (2)
2014-05-01 07:27:39.400 uRSS[742:60b] MVC: oldcat: Misc (7) / newcat: trucs (2)
But: I still get the error:
2014-05-01 07:27:39.400 uRSS[742:60b] MasterViewController::didChangeObject **** Moving something from Misc to trucs****
2014-05-01 07:27:39.401 uRSS[742:60b] *** Assertion failure in -[UITableView _endCellAnimationsWithContext:], /SourceCache/UIKit_Sim/UIKit-2935.137/UITableView.m:1368
2014-05-01 07:27:39.401 uRSS[742:60b] 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 5. 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 (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). with userInfo (null)
I also see that on restarting the app, the Feed has not been moved which usually happened with a save...
UPDATE(5): I reverted to my original didChangeObject
method and it works (moving Feeds across Categories, that is).
How can I get a save to "block" the main UI during its processing? performBlockAndWait
obviously doesn't suffice.