Reputation: 2504
I have a generic set of views/classes for CoreData browsing but am having trouble with the sort order for my fetched results controller after having saved a change to an attribute of one of the listed objects.
In viewWillAppear:
of my table view controller I set up my fetched results controller as such:
- (void) setupFetchedResultsController {
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName: self.entityToList];
request.predicate = self.entitySelectionPredicate; // Typically nil
request.sortDescriptors = self.entitySortDescriptorList;
self.fetchedResultsController = [[NSFetchedResultsController alloc]
initWithFetchRequest: request
managedObjectContext: self.contextForEntity
sectionNameKeyPath: self.keyPathForSections
cacheName: nil]; /* Not chacheing */
}
In didSelectRowAtIndexPath
for this tableview controller I push to a detail table view controller like this:
- (void) tableView: (UITableView *) tableView didSelectRowAtIndexPath: (NSIndexPath *) indexPath {
id objectInCell = [self.fetchedResultsController objectAtIndexPath: indexPath];
ManagedObjectDetailTableViewController *dvc = [[ManagedObjectDetailTableViewController alloc]
initWithStyle: UITableViewStyleGrouped];
dvc.detailItem = objectInCell;
[self.navigationController pushViewController: dvc animated: YES];
}
The ManagedObjectDetailTableViewController has a row for each attribute and each relationship. In didSelectRowAtIndexPath
I push to a ManagedObjectAttributeEditViewController view controller if a row containing and attribute is selected:
- (void) tableView: (UITableView *) tableView didSelectRowAtIndexPath: (NSIndexPath *) indexPath {
// Section 0 has the attributes for the 'detailItem' object alphabetically by name
if( indexPath.section == 0 ) {
ManagedObjectAttributeEditViewController *evc = [[ManagedObjectAttributeEditViewController alloc]
initWithNibName: @"ManagedObjectAttributeEditViewController" bundle: nil];
evc.editedObject = self.detailItem;
evc.delegate = self;
// Figure out from the row which attribute was selected
NSEntityDescription *entity = self.detailItem.entity;
NSDictionary *attributes = entity.attributesByName;
NSArray *keys = [attributes allKeys];
keys = [keys sortedArrayUsingSelector: @selector(compare:)];
NSString *key = [keys objectAtIndex: indexPath.row];
evc.editedFieldKey = key;
[self.navigationController pushViewController: evc animated: YES];
// The other sections are the relationships for the 'detailItem' object
} else {
// Code omitted as not relevant for the error.
}
}
The ManagedObjectAttributeEditViewController has text fields, etc., to allow editing of the attribute's value. When it's save button is touched is executes:
- (IBAction) save {
id valueFromView;
NSAttributeType type = [self typeForEditedAttribute];
switch( type ) {
case NSDateAttributeType:
valueFromView = self.datePicker.date;
break;
case NSStringAttributeType:
if( [self.fieldKeyTester shouldUseTextViewForKey: self.editedFieldKey inEntity: self.editedObject.entity.name] ) {
valueFromView = self.textView.text;
} else {
valueFromView = self.textField.text;
}
break;
case NSInteger16AttributeType:
case NSInteger32AttributeType:
case NSInteger64AttributeType:
valueFromView = [NSNumber numberWithInteger: [self.textField.text integerValue]];
break;
case NSDecimalAttributeType:
case NSDoubleAttributeType:
case NSFloatAttributeType:
valueFromView = [NSNumber numberWithDouble: [self.textField.text doubleValue]];
break;
case NSBooleanAttributeType:
valueFromView = [NSNumber numberWithBool: self.switchControl.isOn];
break;
case NSObjectIDAttributeType:
case NSTransformableAttributeType:
case NSBinaryDataAttributeType:
case NSUndefinedAttributeType:
NSLog( @"Don't know how to handle attribute type: %d in %s", type, __func__ );
break;
default:
NSLog( @"Unrecognized attribute type: %d in %s", type, __func__ );
break;
}
[self.delegate managedObjectAttributeEditViewController: self
didSaveValue: valueFromView forKey: self.editedFieldKey];
}
The ManagedObjectDetailTableViewController is set as the delegate and the didSaveValue:forKey:
method is:
- (void) managedObjectAttributeEditViewController: (ManagedObjectAttributeEditViewController *) controller didSaveValue: (id) value forKey: (NSString *) key {
if( value && key ) {
[self.detailItem setValue: value forKey: key];
NSError *error;
if( ![self.detailItem.managedObjectContext save: &error] ) {
// Update to handle the error appropriately.
NSLog( @"Unresolved error doing save of attribute %@.\n%@", key, error.localizedDescription );
} else {
NSLog( @"-- successfully saved" );
}
} else {
NSLog( @"Got a cancel from edit attribute" );
}
// OK, the attribute editing view controller has told us it is done, pop it
[self.navigationController popViewControllerAnimated: YES];
}
So, if I am starting with the list of objects for the entity, and they are correctly sorted. I touch a row and it pushes to the ManagedObjectDetailTableViewController. I touch an attribute row in that and it pushes to the ManagedObjectAttributeEditViewController. I change the value and touch save. This pops to the ManagedObjectDetailTableViewController, where everything looks fine. I then touch the back button to go back to the list of objects for the entity but now they are no longer sorted (they seem to always be in the same order but I don't recognize a pattern to the order).
If I count to 10 after doing the save and before touching the back button, the list is properly sorted.
If I comment out the [self.detailItem.managedObjectContext save: &error]
method call in didSaveValue:forKey:
then the list of objects for the entity remain correctly sorted but if I exit the application before an autosave has occurred I lose the changes.
This makes me think it has something to do with the [self.detailItem.managedObjectContext save: &error]
having not completed and the fetched results controller (which is using the same NSManagedObjectContext) for some reason not being able to retrieve the data sorted.
The attribute whose value I am changing is not involved in the sort descriptors, so the order displayed should be the same before and after I revise the value. My database is quite large and it may take a few seconds to write it to disk. I am seeing the problem with iOS 5.1 in the simulator and on a device.
Has anyone ever experienced anything like this or have a suggestion?
Sorry this is so long winded and happy 4th of July for all those Stackoverflowers in the USA!
Revised ManagedObjectDetailTableViewController delegate method didSaveValue:forKey:
method is:
- (void) managedObjectAttributeEditViewController: (ManagedObjectAttributeEditViewController *) controller
didSaveValue: (id) value forKey: (NSString *) key {
if( value && key ) {
[self.detailItem setValue: value forKey: key];
NSError *error;
if( ![self.detailItem.managedObjectContext save: &error] ) {
// Update to handle the error appropriately.
NSLog( @"Unresolved error doing save of attribute %@.\n%@", key, error.localizedDescription );
} else {
NSLog( @"-- successfully saved" );
if( [self.detailItem.managedObjectContext.parentContext.hasChanges] ) {
if( ![self.detailItem.managedObjectContext.parentContext save: &error] ) {
NSLog( @"Unresolved error doing save of parent context for attribute %@.\n%@", key, error.localizedDescription );
} else {
NSLog( @"-- successfully saved the parent context too!" );
}
}
}
} else {
NSLog( @"Got a cancel from edit attribute" );
}
// OK, the attribute editing view controller has told us it is done, pop it
[self.navigationController popViewControllerAnimated: YES];
}
I now understand that this double save is necessary for the changes to make it to the permanent store with the latest iOS since with a parent context the save only goes up one level. I don't understand why not having the save propagated to the permanent store should scramble the sort order. Maybe there is some other bug somewhere in my code that this is masking or maybe it is just the way it works...
Upvotes: 3
Views: 447
Reputation: 57168
OK, based on your edits which include the new stack traces:
You're using UIManagedDocument to manage your core data stack. This means the first save is probably from the UIDocument's main thread context and the second is from its background context. Can you confirm that you're using objects from the UIManagedDocument's managedObjectContext context (as opposed to some other context you're making yourself)?
Another thing. Can you confirm that entitySortDescriptorList
is not becoming nil somehow in you setupFetchedResultsController
method?
Finally, as other diagnostics:
save:
from your top level view controller and see what happens.Upvotes: 1