Update: sometimes I get the error message
2013-03-27 19:23:23.094 *** Assertion failure in -[TableView _endCellAnimationsWithContext:], /SourceCache/UIKit_Sim/UIKit-2372/UITableView.m:1070
2013-03-27 19:23:31.280 [53301:c07] CoreData: error: Serious application error. Exception was caught during Core Data change processing. This is usually a bug within an observer of NSManagedObjectContextObjectsDidChangeNotification. Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (4) 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 (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)
2013-03-27 19:23:31.281 [53301:c07] *** 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 (4) 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 (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).'
I have an app which fetches most of it's data from the remote service, and persists it using core data.
I'm writing a view which shows a collection of objects in UITableView with infinite scroll
[Offtopic: One would think that it is fairly typical, but turns out there are at least 20 ways to do that...]
In my viewDidLoad I construct the fetch request, with a fixed small limit, 0 offset, and a fixed batch size. I initiate the NSFetchedResultsController with that request, nil for cache name and nil for the sectionNameKeyPath and call performFetch. Then I attach the data source which takes data from NSFetchedResultsController in a typical way (I only ever have one section)
- (NSInteger) tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section
{
return [[fetchController.resultsController sections][section]
numberOfObjects];
}
- (NSInteger) numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
So far so good, I can see my objects pulled out of DB. The idea is that user sees persisted stuff straight away, until new data comes from network.
Then, inside performBlock on a background ManagedObjectContext (which has main UI ManagedObjectContext as a parent) I perform a network fetch, convert the objects to the core data and push them to the main UI context. That works all right as well.
Trouble starts when NSFetchResultsController starts getting notifications about new objects arriving. I have a typical boilerplate, which is mentioned in several books and in the apple's own documentation:
- (void)controller:(NSFetchedResultsController*)controller
didChangeObject:(id)anObject
atIndexPath:(NSIndexPath*)indexPath
forChangeType:(NSFetchedResultsChangeType)type
newIndexPath:(NSIndexPath*)newIndexPath
{
switch(type) {
case NSFetchedResultsChangeInsert:
[self.table insertRowsAtIndexPaths:#[newIndexPath]
withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[self.table deleteRowsAtIndexPaths:#[indexPath]
withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeUpdate:
[self.delegate
populateCell:[self.table cellForRowAtIndexPath:indexPath]
indexPath:indexPath
];
break;
case NSFetchedResultsChangeMove:
[self.table deleteRowsAtIndexPaths:#[indexPath]
withRowAnimation:UITableViewRowAnimationFade];
[self.table insertRowsAtIndexPaths:#[newIndexPath]
withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
Now things start to get weird. Firstly, the newIndexPath I get is strange: sometimes it's 0, sometimes it's 18, sometimes it's 28. It appears to be non-deterministic (my fetchLimit and batchSize are set to 20)
Furthermore, when insertRowsAtIndexPath is called the assertion is thrown from the cocoa layer:
*** Assertion failure in -[UITableView _endCellAnimationsWithContext:], /SourceCache/UIKit_Sim/UIKit-2380.17/UITableView.m:1070
No, there are no further explanations. Isn't it marvellous?
I take it there is some inconsistency between the number of rows or sections UITableView expects and actually gets. Are there any suggestions in which way should I look? Without UITableView source code I have no idea where to start looking. Currently I'm tempted to re-implement analogue of NSFetchedResultsController myself so that I can see what's actually going on.
did you implement the other delegate methods for the NSFetchedResultController?
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
[self.tableView beginUpdates];
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
[self.tableView endUpdates];
}
- (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];
break;
case NSFetchedResultsChangeDelete:
[self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex]
withRowAnimation:UITableViewRowAnimationFade]; break;
}
}
Related
I am trying to insert and delete datas in core data. But, while updating that in my tableview I am facing error. Insert working fine. While deleting , facing some issue. Issue is below:
Error:
*** Assertion failure in -[UITableView _endCellAnimationsWithContext:], /SourceCache/UIKit_Sim/UIKit-3347.44.2/UITableView.m:1623
2015-11-24 16:21:18.775 datasApp[1898:91463] CoreData: error: Serious application error. Exception was caught during Core Data change processing. This is usually a bug within an observer of NSManagedObjectContextObjectsDidChangeNotification. Invalid update: invalid number of rows in section 0. 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 (3), 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)
2015-11-24 16:21:18.801 datasApp[1898:91463] *** 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 (1) 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 (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).'
***
CODING:
- (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(#"Insrt_before_tblNum_Row---> %ld",(long)[tableView numberOfRowsInSection:newIndexPath.section]);
[tableView insertRowsAtIndexPaths:#[newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
NSLog(#"Insrt_aftr_tblNum_Row---> %ld",(long)[tableView numberOfRowsInSection:newIndexPath.section]);
break;
case NSFetchedResultsChangeDelete:
//THIS LINE WORKING - TABLEVIEW HAVING 4 ROWS
NSLog(#"delete_before_tblNum_Row---> %ld",(long)[tableView numberOfRowsInSection:newIndexPath.section]);
//GETTING CRASH IN THIS
[tableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationFade]; LINE
NSLog(#"delete_aftr_tblNum_Row---> %ld",(long)[tableView numberOfRowsInSection:newIndexPath.section]);
break;
case NSFetchedResultsChangeUpdate:
[self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:[indexPath row] inSection:0] atScrollPosition:UITableViewScrollPositionTop animated:YES];
NSLog(#"Update");
break;
case NSFetchedResultsChangeMove:
[tableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationFade];
[tableView insertRowsAtIndexPaths:#[newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
Deleting a row from a UITableView fed by an NSFetchedResultsController causes my app to crash.
Error is:
* Assertion failure in -[UITableView _endCellAnimationsWithContext:], /SourceCache/UIKit/UIKit-2903.23/UITableView.m:1330
* 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 (1) 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).'
I want only swipe-to-delete. My deletion code goes like this:
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
[self.tableView beginUpdates];
SequenceData *sd = [self.fetchedResultsController objectAtIndexPath:indexPath];
[self.managedObjectContext deleteObject:sd];
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
withRowAnimation:UITableViewRowAnimationFade];
[self.tableView endUpdates];
}
My NSFetchedResultsController is set up just like in Ray Wanderlich's tutorial (http://www.raywenderlich.com/999/core-data-tutorial-for-ios-how-to-use-nsfetchedresultscontroller)
Number of rows is determined by:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
id sectionObjects = [[self.fetchedResultsController sections] objectAtIndex:section];
NSInteger nbObjects = [sectionObjects numberOfObjects];
return nbObjects;
}
It looks like the fetch is not updating (number of rows does not vary). Do I need to fetch myself (isn't this taking care of by the fetch controller)?
There is obviously something basic I am missing here. Don't hesitate to suggest basic answers.
I first implemented this with the controller: didChangeObject: ... method. Error was the same, but some details differed.
Edit
I believe my problem is fixed. Both answers (From CX and Martin) helped me find it. Martin got the answer because of the explanations that helped me understand a little bit better...
Don't call
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
if you use a fetched results controller. Only delete the object with
[self.managedObjectContext deleteObject:sd];
The fetched results controller delegate method didChangeObject: is then called automatically,
and that calls deleteRowsAtIndexPaths:.
So in your case, the row was deleted twice, and that caused the exception.
Note that you don't need beginUpdates/endUpdates here.
If you want to delete a row you need to delete managedObject only.
if (editingStyle == UITableViewCellEditingStyleDelete)
{
[self.managedObjectContext deleteObject:[self.fetchedResultsController objectAtIndexPath:indexPath]];
NSError *error = nil;
if (![self.managedObjectContext save:&error]) {
// handle error
}
}
Deleting the managed object triggers the NSFetchResultController delegate methods, and they will update the tableView.
Edit
You should implement NSFetchResultController delegate method
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath{
switch(type) {
case NSFetchedResultsChangeDelete:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
}
////
default:
break;
}
Because when you work with data source like NSFetchedResultsController, all changes must come from there and your table only reflects them.
I'm currently working on a project where I use UITableView and NSFetchedResultsController to store my data. The problem I'm facing is the use of sections is not working.
The entity i want to be displayed as a tables is of the following character:
Channel (String)
Server (String)
Name (String)
And I want to have sections based on the server and the rows in each section is the channel. The layout will look like this:
Server1
Channel1
Channel2
Server2
Channel3
Channel4
etc..
The problem though is that when I try to insert/remove/change an element in the NSFetchedResultsController the application get an exception as the following:
CoreData: error: Serious application error. An exception was caught from the delegate of NSFetchedResultsController during a call to -controllerDidChangeContent:. Invalid update: invalid number of sections. The number of sections contained in the table view after the update (3) must be equal to the number of sections contained in the table view before the update (2), plus or minus the number of sections inserted or deleted (0 inserted, 0 deleted). with userInfo (null)
I have done some research and people say it's because the UITableViewController Delegate fires first and tries to display an element/section that at the time does not exists. I have yet not found a solution and that is why I'm here.
This is my delegate methods for NSFetchedResultsController
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
{
[self.tableView beginUpdates];
}
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject
atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type
newIndexPath:(NSIndexPath *)newIndexPath
{
UITableView *tableView = self.tableView;
NSLog(#"--- Core Data noticed change");
switch(type) {
case NSFetchedResultsChangeInsert:
[tableView insertRowsAtIndexPaths:#[newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[tableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeUpdate:
[self configureCell:[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
break;
case NSFetchedResultsChangeMove:
[tableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationFade];
[tableView insertRowsAtIndexPaths:#[newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
[self.tableView endUpdates];
}
Many thanks
Robert
You have to implement also the
controller:didChangeSection:atIndex:forChangeType:
delegate function, otherwise the table view will not be notified of inserted or deleted sections in the Core Data store.
You find a sample implementation in the NSFetchedResultsControllerDelegate Protocol Reference.
I have a UITableView that is controlled by a NSFetchedResultsController.
Rows are organised into sections from a property of the row. But when I modify that property the application terminated with:
Invalid update: invalid number of sections.
Each row represents a shot in a film, wide shot, close up etc..
The row also contains which scene it belongs to and the sections are calculated from that.
Shot.m (CoreData entity)
#pragma mark Transient properties
- (NSString *)sectionIdentifier
{
return [self sceneNumber];
}
ShotsViewController.m (The UITableView)
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
id <NSFetchedResultsSectionInfo> theSection = [[self.fetchedResultsController sections] objectAtIndex:section];
return [theSection name];
}
I don't know what i'm supposed to do here. I've played around with the UITableView insertSections and deleteSections but I always get same invalid number of sections error.
Edit
it moves automatically to other existing sections:
- (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];
break;
case NSFetchedResultsChangeDelete:
[self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeUpdate:
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
It worked after I added the
case NSFetchedResultsChangeUpdate:
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
But now, say i've got two cells.
Cell1.sceneNumber = 1
Cell2.sceneNumber = 2
If I set Cell1.sceneNumber = 3 I get the error:
Invalid update: invalid number of sections. The number of sections contained in the table view after the update (3) must be equal to the number of sections contained in the table view before the update (2)
We'd need to see a lot more code to know what the problem is. However, one thing that might be useful is checking out the "Mastering Table Views" video from last year's WWDC. They go over the specifics of how to add/remove sections/rows and the order which you should make the change in your model/table (with clear diagrams).
Go to https://developer.apple.com/videos/wwdc/2010/ and after logging in, go to Application Frameworks > Session 128 - Mastering Table Views.
I have a standard split view controller, with a detail view and a table view. Pressing a button in the detail view can cause the an object to change its placement in the table view's ordering. This works fine, as long as the resulting ordering change doesn't result in a section being added or removed. I.e. an object can change it's ordering in a section or switch from one section to another. Those ordering changes work correctly without problems. But, if the object tries to move to a section that doesn't exist yet, or is the last object to leave a section (therefore requiring the section its leaving to be removed), then the application crashes.
NSFetchedResultsControllerDelegate has methods to handle sections being added and removed that should be called in those cases. But those delegate methods aren't being called for some reason.
The code in question, is boilerplate:
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
NSLog(#"willChangeContent");
[self.tableView beginUpdates];
}
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type {
NSLog(#"didChangeSection");
switch(type) {
case NSFetchedResultsChangeInsert:
[self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject
atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type
newIndexPath:(NSIndexPath *)newIndexPath {
NSLog(#"didChangeObject");
UITableView *tableView = self.tableView;
switch(type) {
case NSFetchedResultsChangeInsert:
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeUpdate:
[self configureCell:[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
break;
case NSFetchedResultsChangeMove:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
NSLog(#"didChangeContent");
[self.tableView endUpdates];
[detailViewController.reminderView update];
}
Starting the application, and then causing the last object to leave a section results in the following output:
2011-01-08 23:40:18.910 Reminders[54647:207] willChangeContent
2011-01-08 23:40:18.912 Reminders[54647:207] didChangeObject
2011-01-08 23:40:18.914 Reminders[54647:207] didChangeContent
2011-01-08 23:40:18.915 Reminders[54647:207] *** Assertion failure in -[UITableView _endCellAnimationsWithContext:], /SourceCache/UIKit_Sim/UIKit-1145.66/UITableView.m:825
2011-01-08 23:40:18.917 Reminders[54647:207] Serious application error. Exception was caught during Core Data change processing: Invalid update: invalid number of sections. The number of sections contained in the table view after the update (5) must be equal to the number of sections contained in the table view before the update (6), plus or minus the number of sections inserted or deleted (0 inserted, 0 deleted). with userInfo (null)
As you can see, "willChangeContent", "didChangeObject" (moving the object in question), and "didChangeContent" were all called properly. Based on the Apple's NSFetchedResultsControllerDelegate documentation "didChangeSection" should have been called before "didChangeObject", which would have prevented the exception causing the crash.
So I guess the question is how do I assure that didChangeSection gets called?
Thanks in advance for any help!
This problem was caused by using a transient attribute as the sectionNameKeyPath. When I instead stored the attribute used for the sectionNameKeyPath in the database, the problem went away. I don't know if there is a way to get the sections updated based on a NSFetchedResultsController content changes when using a transient attribute as a sectionNameKeyPath. For now I am considering this a limitation of transient attributes.
I am doing the same thing in my application (transient property in the sectionNameKeyPath) and am not seeing the problem you are experiencing. I am testing this on iOS 4.2.1... There is a known bug where you cant trust any of the FRC delegate callbacks in iOS 3.X, you have to do a full [tableView reloadData] in the controllerDidChangeContent: message. see the FRC documentation
I have tested going from an existing section with another entry in it to a nonexistent section as well as from a section with only one row to another nonexistent section.