Understanding how NSFetchedResultsController works - iphone

First off, thank you very much if you find time to help me!
I have been learning about NSFetchedResultsControllerand im trying to understand it as much as i can, however, some things about it very much confuse me. This code if from a tutorial i've found online. Basically i have the simplest fetched controller set up with really fancy methods like this
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
// The fetch controller is about to start sending change notifications, so prepare the table view for updates.
[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 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];
// Reloading the section inserts a new row and ensures that titles are updated appropriately.
[tableView reloadSections:[NSIndexSet indexSetWithIndex:newIndexPath.section] withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
(NSFetchedResultsController *)fetchedResultsController {
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"FailedBankInfo" inManagedObjectContext:_context];
[fetchRequest setEntity:entity];
NSSortDescriptor *sort = [[NSSortDescriptor alloc]
initWithKey:#"details.closeDate" ascending:NO];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sort]];
[fetchRequest setFetchBatchSize:20];
NSFetchedResultsController *theFetchedResultsController =
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:_context sectionNameKeyPath:nil
cacheName:#"Root"];
self.fetchedResultsController = theFetchedResultsController;
_fetchedResultsController.delegate = self;
[sort release];
[fetchRequest release];
[theFetchedResultsController release];
return _fetchedResultsController;
}
Do i need to create a new fetched controller everytime for different view controllers? For instance if i have a list of businesses in one list view. then i click on one of them and brings me to a list of employees in that ocmpany, i have to use a different fetchedViewController since i need to respecify the entity key right? See above my implementation.
With my current implementation, it works great for listing items. Let's say I have a view controller called VC. init i fully implemented NSFetchedResultsController. I have a barbutton add item and a method called addButtonPressed which when pressed modally adds a view (from bottom up). In this same method i have
Entity myEntity = [NSEntityDescription insertNNewObjectForEntityForName:#"Entity" inManagedObjectContext:context_]; Then i tell the code the go into another view. HOWEVER, when the animation is moving the new navigation controller up, the new cell already shows itself mid animation. From my understanding this these two methods
- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath AND - (UITableViewCell *)tableView:(UITableView *)tableView get called in conjuction with the top two methods in the code snippet above. how can i fix it? i dont want anything showing up on the current view until i added a new Entity by going to another view. The second another object gets called into the context, the update methods must get called or something
What does this snippet of code do? Particularly the part with id < stuff >
(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;
}
}
Edit: Holy $#$, formatting this frustrating. there should be didChangeSection:(id (LESS THAN EQUAL SIGN) NSFetchedResultsSectionInfo (GREATER THEN EQUAL SIGN)) in the code above
4 Where can i learn more about this. i.e what the individual content updating methods do and when they are called etc. Also Does the NSFetchedResultcontroller act as a single array? Meaning that if i want to have a tableview with multiple sections i need more ViewControllers? Once again THANKS!

I second Brad's comment, but I'll give you some pointers:
Basically yes, you need a new NSFetchedResultsController for every entity you want to query for. However, you don't need to use and NSFetchedResultsController at all, this is just a nice class to let you do fancy things with your tableView as you are doing in question #2.
Your problem here is that you are calling [NSEntityDescription insertNNewObjectForEntityForName:#"Entity" inManagedObjectContext:context_]; from your tableViewController and as soon as you call that your NSFetechedResultsController will get a message stating that a row was inserted, triggering the NSFetchedResultsChangeInsert path in your controller:didChangeObject:atIndexPath:forChangeType:newIndexPath: method. NSFetchedResultsController is an object that watches the data that is fetches from CoreData for any changes and then gives you the ability to do stuff as the changes happen. To fix your display issue, just move the insertNewObjectForEntityName call into your next viewController, and magically when you dismiss that viewController you'll have a new row.
The didChangeSection: delegate method of NSFetchedResultsController is informing you if a section has been added or deleted from your dataset. See my answer to #4 and this should be clear.
NSFetechedResultsController allows you to "section" the data it fetches using the sectionNameKeyPath: part of the init call. By supplying the name of a data point in your entity you can group your data into sections. So going back to #3 & #2 if you happen to insert a new entity that has a new value in the "section" keypath that you specified when initing the NSFetchedResultsController you'd get the didChangeSection: delegate called.
I recommend reading Apples documentation on NSFetchedResultsController and CoreData and probably their TableView Programming Guide. Don't forget to download and play with the sample code, it's easier to understand in most cases than the theories in the documentation.

Related

NSFetchedResultsController add entity inserts blank row in tableView

In my app I have a viewcontroller which is the delegate of NSFetchedResultsController and the delegate and datasource of a UITableView.
When an add button in the navigation controller is pressed, the next view is pushed correctly and I can correctly add a new Person entity.
My issue is that when the add button is pressed, a blank row is added to the tableView as the new view is pushed, and is still there after the new entity record is correctly created
This is the target action for the add button:
- (void)addPerson:(id)sender
{
AddPersonViewController *addPersonController = [[AddPersonViewController alloc] initWithNibName:#"AddPersonViewController" bundle:nil];
[addPersonController setPerson:[NSEntityDescription insertNewObjectForEntityForName:#"Person" inManagedObjectContext:self.managedObjectContext]];
[self.navigationController pushViewController:addPersonController animated:YES];
}
The code that creates the blank row (from the apple docs for the NSFetchedResultsControllerDelegate) is here:
- (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:[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;
}
}
Any ideas how to stop this blank row being created?
Calling
[NSEntityDescription insertNewObjectForEntityForName:#"Person" inManagedObjectContext:self.managedObjectContext]
in addPerson creates a new Person object and adds it to the managed object context. If the fetched results controller of the table view is configured to get all Person objects, this will result in a new (blank) table row for this new object.
I don't know how your AddPersonViewController works. If it modifies the object given via setPerson, then the table row should be updated. If it creates a new Person object, then the first (blank) entry will remain.
You should probably delay the creating of the new Person object until the AddPersonViewController has all data to actually create and populate the object. You could do this either by moving the insertNewObjectForEntityForName: call to the AddPersonViewController, or by using a delegate method in your table view controller that is called from the AddPersonViewController.

NSFetchedResultsController not displaying correct sort until after saveContext saves?

Admittedly I'm new. Criticism welcomed.
After a month of trying various tweaks, the problem remains. For simplicity: There are three view controllers, calling them A, B, & C.
A has 2 round rect buttons that each do a push segue to B.
B is a tableview filled in by the troublesome 'NSFetchedResultsController' described. B has a Nav bar button (Add) to segue to C which accepts two data fields and saves the new item then pops View C and shows View B.
Adding new item from View C and returning to View B results in a correctly sorted list. But if I return from View B back to View A, then segue again to View B, the list is not sorted correctly. Each time View B appears it creates a new Fetched Results Controller. Back to A, Back to B the view is not sorted correctly. Repeat - same problem. But return back to A, wait 3 seconds, and then segue to B and woohoo the list is sorted correctly. I've tried it with the cache set to nil, and set to a value that is deleted when the view disappears.
Every subsequent new display of View B will show the list correctly sorted. The wrong sort sequence only occurs in the sequence described above - after adding 1 item, going back one view and then redisplaying the table view fed by the FRC.
Questions and suggestions are welcomed. Thanks in advance. The rest of the original question is shown below;
I've a NSFetchedResultsController filling a table view in alphabetic order, with a segue to another view to add an item. When I return to the main table, the sort is random. I can exit from the view to a prior view on the hierarchy and reload the view and it will still be randomly sorted. I can repeat this several times in succession. However, if I wait a few seconds, and follow the same steps, the view will sort correctly. It appears that the correct sort happens when reading from the saved file, which I understand happens "sometime later" automatically. Cache is set to nil.
I have a saveContext call and then a reloadData call to the table view in the method that adds an item, trying to force the data to save and then reload the table. I have the NSFetchedResultsControllerDelegate set to the view.
Any suggestions?
The code for the Fetched Results Controller is as follows:
- (NSFetchedResultsController *)fetchedResultsController {
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"Player"];
[fetchRequest setFetchBatchSize:20];
NSSortDescriptor * lastNameSort = [NSSortDescriptor sortDescriptorWithKey:#"lastName" ascending:YES selector:#selector(localizedCaseInsensitiveCompare:)];
NSSortDescriptor * firstNameSort = [NSSortDescriptor sortDescriptorWithKey:#"firstName" ascending:YES selector:#selector(localizedCaseInsensitiveCompare:)];
fetchRequest.sortDescriptors = [NSArray arrayWithObjects: lastNameSort, firstNameSort, nil];
NSFetchedResultsController * frc = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:nil];
frc.delegate = self;
NSError *error = nil;
if (![frc performFetch:&error]) {
// Handle the error
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
_fetchedResultsController = frc;
return _fetchedResultsController;
}
- (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];
break;
case NSFetchedResultsChangeDelete:
[self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex]
withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath {
Player * player = [self.fetchedResultsController objectAtIndexPath:indexPath];
cell.textLabel.text = player.fullName;
}
- (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:[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 {
[self.tableView endUpdates];
}

NSFetchedResultsController – getting exception when a new section is created (UPDATE: might be when all rows in a section is deleted...)

I've got a view with a TableView and an NSFetchedResultsController.
I'm using a ASINetworkQueue (subclass of NSOperationQueue) and a subclass of ASIHTTPRequest (which in turn is a subclass of NSOperation) to download a JSON feed, parse it and insert corresponding entities into Core Data.
Therefore, in the ASIHTTPRequest subclass, I've got a second NSManagedObjectContext to keep everything threadsafe and nice.
Everything is fine, my background fetching/import fires of each 10 seconds or so, new entities are created and saved into the Core Data store. The NSNotification propagates its way to the ViewController and the NSFetchedResultsController and new rows appear in the TableView.
The problem occurs when the JSON contains an entity with a new value of the section key (lets call it "sectionID") – for example sectionID == 2 instead of sectionID == 1 (you get it?).
At this point, the NSFetchedResultsController should make the table view create a new section, but instead I'm getting an exception:
Serious application error. Exception was caught during Core Data change processing. This is usually a bug within an observer of NSManagedObjectContextObjectsDidChangeNotification. *** -[NSArray initWithObjects:count:]: attempt to insert nil object at objects[0] with userInfo (null)
Assertion failed: (_Unwind_SjLj_Resume() can't return), function _Unwind_SjLj_Resume, file /SourceCache/libunwind/libunwind-24.1/src/Unwind-sjlj.c, line 326.
Here is my code for the NSFetchedResultControllers delegate methods:
-(void)controllerWillChangeContent:(NSFetchedResultsController *)controller
{
[[self eventTable] beginUpdates];
}
-(void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type {
NSIndexSet* set = [NSIndexSet indexSetWithIndex:sectionIndex];
switch (type) {
case NSFetchedResultsChangeInsert:
[[self eventTable] insertSections:set withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[[self eventTable] deleteSections:set withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
-(void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {
UITableView* tv = [self eventTable];
switch (type) {
case NSFetchedResultsChangeInsert:
[tv insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[tv deleteRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeUpdate:
[self tableView:tv configureCell:[tv cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
break;
}
}
-(void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
[[self eventTable] endUpdates];
}
Any thoughts about whats causing the exception? Thanks in advance!
UPDATE 2011-02-03
Not really sure whether the error occurs when a new section is created, or an old one is deleted. I almost think that this occurs when all the rows in a section is deleted, but by some reason the controller:didChangeSection:atIndex:forChangeType is not being called.
Anyone with experience of something similar?
UPDATE 2011-02-08
I think I solved it. The problem was that some extra conditions had to be taken into concern when determining whether to delete the row/section or not.
When using the code supplied in the Apple documentation (along with a few adjustments to make it work in my view), it runs OK.
I think I solved it. The problem was that some extra conditions had to be taken into concern when determining whether to delete the row/section or not. When using the code supplied in the Apple documentation (along with a few adjustments to make it work in my view), it runs OK.
UPDATE 2011-03-22
Basically I used the same approach as in SafeFetchedResultsController.
I don't know if this is the reason for your error but you are missing the NSFetchedResultsChangeMove case in your delegate methods.
case NSFetchedResultsChangeMove:
[changedTableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
[changedTableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]withRowAnimation:UITableViewRowAnimationFade];
break;

App is crashing when deleting from UITableView due to self referencing connection

I'm brand new to iPhone development (and first question posted here) and am sort of stuck with Core Data and Table Views.
In short, my app is crashing when I delete a row from my UITableView due to NSFetchedResultsChangeUpdate being called on a record that has already been removed due to a cascade delete on a self referring table.
Here is a description of the data model:
There are two Entities; Person and Connection.
Person contains name (String), connections (To Many Relationship to Connection->source, cascade delete rule) and connectedby (To Many Relationship to Connection->connection, cascade delete rule)
Connection contains relationship (String), source (Relationship to Person->connections, nullify delete rule) and connection (Relationship to Person->connectedby, nullify delete rule)
The idea being that there are two people connected by a relationship (eg Mother or Son)
In my TableViewController I implement the following:
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
[self.tableView beginUpdates];
}
and
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {
UITableView *tableView = self.tableView;
Person *person = nil;
switch(type) {
case NSFetchedResultsChangeInsert:
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeUpdate:
person = (Person *)[fetchedResultsController objectAtIndexPath:indexPath];
[self configureCell:(PersonTableViewCell *)[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
break;
case NSFetchedResultsChangeMove:
person = (Person *)[fetchedResultsController objectAtIndexPath:indexPath];
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
and
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
[self.tableView endUpdates];
}
Here are the sample records I created for testing this:
Person *person1 = [NSEntityDescription insertNewObjectForEntityForName:#"Person" inManagedObjectContext:self.managedObjectContext];
[person1 setName:[NSString stringWithFormat: #"Tommy"]];
Person *person2 = [NSEntityDescription insertNewObjectForEntityForName:#"Person" inManagedObjectContext:self.managedObjectContext];
[person2 setName:[NSString stringWithFormat: #"Jane"]];
Connection *connection = [NSEntityDescription insertNewObjectForEntityForName:#"Connection" inManagedObjectContext:managedObjectContext];
[connection setConnection:person2];
[connection setSource:person1];
[connection setRelationship:[NSString stringWithFormat:#"Mother"]];
Connection *connection2 = [NSEntityDescription insertNewObjectForEntityForName:#"Connection" inManagedObjectContext:managedObjectContext];
[connection2 setConnection:person1];
[connection2 setSource:person2];
[connection2 setRelationship:[NSString stringWithFormat:#"Son"]];
When I delete the record at indexPath[0,0] i.e. Jane in this example since the view is sorted by name, I generate the following error:
2010-10-19 16:09:01.461 HelpMe[6324:207]
Serious application error.
Exception was caught during Core Data change processing.
This is usually a bug within an observer of NSManagedObjectContextObjectsDidChangeNotification.
*** -[NSMutableArray objectAtIndex:]: index 1 beyond bounds [0 .. 0] with userInfo (null)
Detected an attempt to call a symbol in system libraries that is not present on the iPhone:
_Unwind_Resume called from function -[NSManagedObjectContext(_NSInternalChangeProcessing) _processRecentChanges:] in image CoreData.
The delete seems to correctly generate a NSFetchedResultsChangeDelete for indexPath [0,0] but also then immediately generates a NSFetchedResultsChangeUpdate for [0,1] which no longer exists since [0,1] is seemingly now in [0,0] after the delete.
Without the associated Connection record, it deletes fine.
I can seemingly work around this by simply calling [self.tableView reloadData] on controllerDidChangeContent instead of implementing begin/end updates and didChangeOnject: but I do not believe this is the proper way to handle this.
I appreciate any help anyone can offer.
I resolved a similar issue this by providing a non-nil sectionNameKeyPath to NSFetchedResultsController (in initWithFetchRequest).
I then avoided sections by using:
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
return #"";
}
Very annoying.
FYI, my issue was this error:
Serious application error. An
exception was caught from the delegate
of NSFetchedResultsController during a
call to -controllerDidChangeContent:.
* -[NSMutableArray objectAtIndex:]: index 0 beyond bounds for empty array
with userInfo (null)
At the top of - (void)controller:(NSFetchedResultsController *)controller didChangeObject: I was fetching the entity out of the fetchedResultsController. Well, that really messes up things when you have just deleted the object and need to update the table. I moved the entity fetch into the NSFetchedResultsChangeUpdate portion. This then kept me from crashing out.

How can I get my table view to redisplay data when objects are deleted?

I have table view which is in a view controller that inherits from UITableViewController.
I use a NSFetchedResultsController that I use to fetch the table data from my core data store. I've tried it with both caching on and off.
I set the delegate of the NSFetchedResultsController to be self and I've implemented controllerDidChangeContent in my view controller.
I've always implemented the table view delegate functions including commitEditingStyle which is called when the user deletes a row.
Here is what happens. The user swipes and deletes a row and commitEditingStyle is called as expected. In that function, I modify my core data objects to so that they are effectively deleted from results. In other words, if you ran the query I passed NSFetchedResultsController again, that row would now no longer be in the results set.
This alone is not enough, so I added a called to tableView reloadData at the end of commitEditingStyle. Still no luck, so I added a refresh button that calls reloadData and that doesn't help either. Going to another view controller (by hitting "back") and returning to the page doesn't usually work, but eventually it figures it out and the row will disappear correctly. Rerunning the program from scratch always works, of course, but how can I get my tableView to update correctly? controllerDidChangeContent never gets called by the way. I thought reloadData would be enough, but it doesn't seem to be.
Have you implemented the Fetched results controller's controller didChangeObject: delegate method?
The easiest way is to create a new project using navigation based template with core-data on and compare the RootViewController with your code. RootViewController already contains the code to perform the update after tableview modification.
It is something like this:
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
// Delete the managed object for the given index path
NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
[context deleteObject:[self.fetchedResultsController objectAtIndexPath:indexPath]];
// Save the context.
NSError *error = nil;
if (![context 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();
}
}
}
Then use this method to update the table view
- (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:[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;
}