I am using core data fetchResultController as table data source. All works fine but running into peculiar issue on deleting bulk items from coredata. Getting bellow error and table becoming blank
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 (339) must be equal to the number of rows contained in that section before the update (338), 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 am using below code
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
[self.dataTableView beginUpdates];
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
[self.dataTableView endUpdates];
}
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {
UITableView *tableView = self.dataTableView;
switch(type) {
case NSFetchedResultsChangeDelete:{
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
withRowAnimation:UITableViewRowAnimationAutomatic];
}
}
UPDATE:
Here is my numberOfRows method
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
if ([self fetchedResultsController]) {
id sectionInfo = [[[self fetchedResultsController] sections] objectAtIndex:0];
NSInteger rowCount = [sectionInfo numberOfObjects];
return rowCount;
}
return 0;
}
Related
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.
Objective: Using FRC, sort Section's by startDate, an NSDate attribute, but want Today's date Section to appear before Upcoming dates Section.
I followed Apple's code using a transient property sectionIdentifier. Apple's sample code. and started with this project first: OneFRC
I soon realized that this may not be possible with just one FRC (I could be wrong).
Next, I decided to take a stab at this with 3 FRCs: ThreeFRC.
TableView sections now appears in the Order that I want:
Section 0: Today
Section 1: Upcoming
Section 2: Past
However, adding data triggers FRC delegates, and I get the following 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 (4) 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)
Again, I would love to be able to accomplish my objective with 1 FRC, but I can't seem to figure out how.
I have been trying to resolve this for 4 days now! If this issue doesn't get resolved on SO, I think I may reach out to Apple for Developer support. And in the event that I do, I'll post the resolution here so others can benefit.
Projects are available on Github:
One FRC
Three FRC
EDIT
Thanks to #blazejmar, I was able get rid of the rows error. However, now I get an error when I attempt to add sections.
2014-11-03 16:39:46.852 FRC[64305: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 sections.
The number of sections contained in the table view after the update (2) must be
equal to the number of sections contained in the table view before the update (1),
plus or minus the number of sections inserted or deleted (0 inserted, 0 deleted).
with userInfo (null)
Steps to reproduce the error in Three FRC:
1. Launch App ->
2. Tap Generate Data ->
3. Tap View in FRC ->
4. Tap back to the RootVC ->
5. Change the system date to a month from Today ->
6. Tap View in FRC and only one section `Past` should appear. ->
7. Tap `Add Data`.
8. The error should appear in the log.
In your ThreeFRC project there are some issues:
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
[self.tableView beginUpdates];
self.numberOfSectionsInTV = 0;
[self fetchData];
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
[self.tableView reloadData];
[self.tableView endUpdates];
}
You shouldn't use fetchData inside FRC delegate. Methods are called in proper order (before, during and after update) so inside callbacks you have consistent state of context. Also it's not the best idea to use reloadData before endUpdates(it's applying all changes you provided earlier) and reloadData is erasing everything and building it from scratch. This is most likely causing the crash.
Other thing I've spotted that may be buggy is handling of updates. If you have 3 separate FRC without sections you won't get section update callback in FRC delegate. But if some objects appear in one of the FRC's then you should detect that and manually insert them.
Using just reloadData in controllerDidChangeContent would be enough, but this isn't the best solution, as you won't get any animations. The proper way would be to handle all the cases: deleting all objects from one of FRCs (and then deleting section manually from TableView), inserting first object into FRC (then you should create new section at proper indexPath).
i looked at your ThreeFRC project and noticed that in - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView you check which FRCs contain objects and that would determine the number of sections. this makes logical sense, but really confuses the FRC delegate when adding/deleting "sections" (or, when your other FRCs suddenly have objects). For example, you only have a Past section (1 section), but then the data changes such that you now also have a Today section. Since sectionPastFRC or the other FRCs didn't have any section changes, there are no calls to - (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type, and though you should have 2 sections now, there were no calls to add, delete, or move sections. you'd have to update the sections manually somehow, which may be a pain.
here's the workaround i suggest: since you will ALWAYS have at most one section for each FRC, you should just return 3 in - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView. This is so there will no longer be any problem in adding/deleting a section because they were all already there. Anyway, if, for example, the Today section has no objects, just return 0 in - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section. Just make sure that in - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section, if fetchedObjects==0, return nil, so that it also won't display the section header if that section has no objects. And in your FRC delegate didChangeObject, just always adjust the indexPath and newIndexPath before performing changes on the tableView.
note that this workaround will only work if you already know the maximum number of sections that the FRCs (except the last FRC) will need. it is NOT a solution for all implementations of multiple FRCs in a single table view. i've actually used this solution in a project where i had 2 FRCs for one tableView, but the first FRC would only always take up 1 section, while the second FRC could have any number of sections. i always just had to adjust the sections +1 for changes in the second FRC.
i've actually tried applying the changes i mentioned above into your code, and haven't been getting errors. here are the parts i changed in the UITableViewDataSource:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 3;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSInteger rows = 0;
switch (section) {
case 0:
{
rows = [[self.sectionTodayFRC fetchedObjects]count];
break;
}
case 1:
{
rows = [[self.sectionUpcomingFRC fetchedObjects]count];
break;
}
case 2:
{
rows = [[self.sectionPastFRC fetchedObjects]count];
break;
}
}
NSLog(#"Section Number: %i Number Of Rows: %i", section,rows);
return rows;
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
NSString *header;
switch (section) {
case 0:
{
if ([[self.sectionTodayFRC fetchedObjects]count] >0)
{
header = #"Today";
}
break;
}
case 1:
{
if ([[self.sectionUpcomingFRC fetchedObjects]count] >0)
{
header = #"Upcoming";
}
break;
}
case 2:
{
if ([[self.sectionPastFRC fetchedObjects]count] >0)
{
header = #"Past";
}
break;
}
}
return header;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:CellIdentifier];
}
Meeting *meeting;
switch (indexPath.section) {
case 0:
if ([[self.sectionTodayFRC fetchedObjects]count] > 0)
{
meeting = [[self.sectionTodayFRC fetchedObjects] objectAtIndex:indexPath.row];
}
break;
case 1:
if ([[self.sectionUpcomingFRC fetchedObjects]count] > 0)
{
meeting = [[self.sectionUpcomingFRC fetchedObjects] objectAtIndex:indexPath.row];
}
break;
case 2:
if ([[self.sectionPastFRC fetchedObjects]count] > 0)
{
meeting = [[self.sectionPastFRC fetchedObjects] objectAtIndex:indexPath.row];
}
break;
}
cell.textLabel.text = meeting.title;
return cell;
}
and for the NSFetchedResultsControllerDelegate:
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {
NSLog(#"Inside didChangeObject:");
NSIndexPath *modifiedIndexPath;
NSIndexPath *modifiedNewIndexPath;
if (controller == self.sectionTodayFRC)
{
modifiedIndexPath = [NSIndexPath indexPathForRow:indexPath.row inSection:0];
modifiedNewIndexPath = [NSIndexPath indexPathForRow:newIndexPath.row inSection:0];
}
else if (controller == self.sectionUpcomingFRC)
{
modifiedIndexPath = [NSIndexPath indexPathForRow:indexPath.row inSection:1];
modifiedNewIndexPath = [NSIndexPath indexPathForRow:newIndexPath.row inSection:1];
}
else if (controller == self.sectionPastFRC)
{
modifiedIndexPath = [NSIndexPath indexPathForRow:indexPath.row inSection:2];
modifiedNewIndexPath = [NSIndexPath indexPathForRow:newIndexPath.row inSection:2];
}
switch(type) {
case NSFetchedResultsChangeInsert:
[self.tableView insertRowsAtIndexPaths:#[modifiedNewIndexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
break;
case NSFetchedResultsChangeDelete:
NSLog(#"frcChangeDelete");
[self.tableView deleteRowsAtIndexPaths:#[modifiedIndexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
break;
case NSFetchedResultsChangeUpdate:
NSLog(#"frcChangeUpdate");
[self.tableView reloadRowsAtIndexPaths:#[modifiedIndexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
break;
case NSFetchedResultsChangeMove:
NSLog(#"frcChangeDelete");
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:modifiedIndexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:modifiedNewIndexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
break;
}
}
i hope this helps someone!
I've got a UITableView backed by an array of Core Data objects, made by appending two arrays of Core Data objects named folders and notes. Whenever I try to delete a row from the table view, it throws:
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 (2) 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, 1 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).'
Here's the code to delete (in my UITableViewCell subclass):
NSManagedObjectContext *context = [NSManagedObjectContext MR_contextForCurrentThread];
[object MR_deleteInContext:context];
[context MR_saveToPersistentStoreAndWait];
[tableViewController loadData];
[tableView deleteRowsAtIndexPaths:#[self.indexPath] withRowAnimation:UITableViewRowAnimationLeft];
Note: the MR_ methods are MagicalRecord methods, they do what they say.
Here's the loadData method in the UITableViewController (basically refetches the array):
NSPredicate *textParentFilter = [NSPredicate predicateWithFormat:#"parent == %#", self.parent];
self.notes = [Note MR_findAllWithPredicate:textParentFilter];
NSPredicate *folderParentFilter = [NSPredicate predicateWithFormat:#"parentFolder == %#", self.parent];
self.folders = [Folder MR_findAllWithPredicate:folderParentFilter];
Also:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
// Return the number of rows in the section.
return [self.notes count] + [self.folders count];
}
I can see why I'm getting an exception: I'm deleting the row in loadData and then the numberOfRowsInSection is out of sync from the tableview and the Core Data store. However, if I replace deleteRowsAtIndexPaths with [tableView reloadData], it deletes just fine (but there's no animation). I've tried beginUpdates and endUpdates, switching the order in which things are deleted, to no avail.
When working with UITableViews and Core Data, it is important that the objects in your storage (e.g., your notes and folders arrays) match the UITableView delegate/datasource calls at all times. When you start animating cells for addition, deletion and updating, you encounter synchronisation issues where occasionally, the values don't align. A good example is when you have to perform multiple cell additions/deletions in succession while showing or revealing a section of a table. If you use [tableView reloadData], you enforce synchronisation but won't be able to animate the cells.
NSFetchedResultsController can be used to synchronise multiple cell additions/deletions with animations. It will require a bit of rewiring on your end, but not as much as you'd think. With this, you can control animation styles, even bulk modify cells while keeping your table intact. NSFetchedResultsController will listen to changes in Core Data based on a predicate, and reflect these changes in the tableview. This means you can have multiple tableviews respond with their own NSFetchedResultsController without having to notify them, and everything will stay synchronised.
A good place to find skeleton code is to set up a new Core Data project in XCode. Methods that you'll need to add to your code (found in MasterViewController.m):
// this references your controller that listens to Core Data and communicates with the tableview
- (NSFetchedResultsController *)fetchedResultsController
// delegate methods:
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller;
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type;
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject
atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type
newIndexPath:(NSIndexPath *)newIndexPath;
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller;
Your tableview will then need to consult the NSFetchedController instance for it's delegate/datasource methods. For example:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return [[self.fetchedResultsController sections] count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
id <NSFetchedResultsSectionInfo> sectionInfo = [self.fetchedResultsController sections][section];
return [sectionInfo numberOfObjects];
}
If you're feeling advanced enough, there's a great tutorial that shows how you can queue cells additions/deletions for improved performance:
http://www.fruitstandsoftware.com/blog/2013/02/uitableview-and-nsfetchedresultscontroller-updates-done-right/
NSFetchedResultsController is one of those things that you don't really hear about in a basic Core Data tutorial, but it proves to be a real asset when doing table animations and handling large amounts of data (through caching).
I use the following code whenever a user slides a cell to delete it:
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (editingStyle == UITableViewCellEditingStyleDelete)
{
if (tableView == self.tableView)
{
NSInteger row = [indexPath row];
Entry *e = self.entries[row];
[self.entries removeObjectAtIndex: indexPath.row];
[e MR_deleteEntity];
[self.tableView reloadData];
}
}
}
In your case you first need to figure out if 'row' corresponds to a Note or a Folder, but the idea is the same.
I've fixed it! Or rather, NSFetchedResultsController fixed it. I've implemented
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath
in my view controller to deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:. My delete method is now just
NSManagedObjectContext *context = [NSManagedObjectContext MR_defaultContext];
[object MR_deleteInContext:context];
[context MR_saveToPersistentStoreAndWait];
(thanks for the advice about MR_contextForCurrentThread).
Here's loadData:
NSManagedObjectContext *c = [NSManagedObjectContext MR_defaultContext];
NSPredicate *textParentFilter = [NSPredicate predicateWithFormat:#"parent == %#", self.parent];
self.notes = [Note MR_fetchAllSortedBy:#"text" ascending:YES withPredicate:textParentFilter groupBy:nil delegate:self inContext:c];
NSPredicate *folderParentFilter = [NSPredicate predicateWithFormat:#"parentFolder == %#", self.parent];
self.folders = [Folder MR_fetchAllSortedBy:#"title" ascending:YES withPredicate:folderParentFilter groupBy:nil delegate:self inContext:c];
And - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section:
if ([self.notes.sections count] + [self.folders.sections count] > 0) {
id <NSFetchedResultsSectionInfo> notesSectionInfo = [self.notes.sections objectAtIndex:section];
id <NSFetchedResultsSectionInfo> foldersSectionInfo = [self.folders.sections objectAtIndex:section];
return [notesSectionInfo numberOfObjects] + [foldersSectionInfo numberOfObjects];
} else
return 0;
I've got an UITableView that uses an NSFetchedResultsController as it's data source. I use one of the fields for my sections. I enabled row deletion (using the swipe gesture). Works just fine.
The problem comes up when I delete the last row of a section. It does not crash on me but the console shows the following message:
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 (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). with userInfo (null)
This is all pretty basic stuff:
- (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;
switch(type) {
…
case NSFetchedResultsChangeDelete:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeUpdate:
[tableView reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
[self configureCell:[tableView cellForRowAtIndexPath:indexPath]
atIndexPath:indexPath];
break;
…
}
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
[[self tableView] endUpdates];
}
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
// Delete the row from the data source
FHClass *foo = [[self fetchedResultsController] objectAtIndexPath:indexPath];;
[[self managedObjectContext] deleteObject:meal];
[self saveContext];
}
}
If I check -numberOfRowsInSection: I see that the number is steadily decreasing when deleting rows. Even to the point where it says 0.
What did I miss? Any hints? Stupid mistake on my part? ;-)
You should implement controller:didChangeSection:atIndex:forChangeType: to deal with section changes and then request the table view to deleteSections:.
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.