I have an app based on the CoreDataBooks example that uses an addingManagedObjectContext
to add an Ingredient
to a Cocktail
in order to undo the entire add. The CocktailsDetailViewController
in turn calls a BrandPickerViewController
to (optionally) set a brand name for a given ingredient. Cocktail
, Ingredient
and Brand
are all NSManagedObjects
. Cocktail
requires at least one Ingredient
(baseLiquor
) to be set, so I create it when the Cocktail
is created.
If I add the Cocktail
in CocktailsAddViewController : CocktailsDetailViewController
(merging into the Cocktail managed object context on save) without setting baseLiquor.brand
, then it works to set the Brand
from a picker (also stored in the Cocktails managed context) later from the CocktailsDetailViewController
.
However, if I try to set baseLiquor.brand
in CocktailsAddViewController
, I get:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Illegal attempt to establish a relationship 'brand' between objects in different contexts'
From this question I understand that the issue is that Brand
is stored in the app's managedObjectContext
and the newly added Ingredient
and Cocktail
are stored in addingManagedObjectContext
, and that passing the ObjectID
instead would avoid the crash.
What I don't get is how to implement the picker generically so that all of the Ingredients (baseLiquor
, mixer
, garnish
, etc.) can be set during the add, as well as one-by-one from the CocktailsDetailViewController
after the Cocktail
has been created. In other words, following the CoreDataBooks example, where and when would the ObjectID
be turned into the NSManagedObject
from the parent MOC in both add and edit cases? -IPD
UPDATE - Here's the add method:
- (IBAction)addCocktail:(id)sender {
CocktailsAddViewController *addViewController = [[CocktailsAddViewController alloc] init];
addViewController.title = @"Add Cocktail";
addViewController.delegate = self;
// Create a new managed object context for the new book -- set its persistent store coordinator to the same as that from the fetched results controller's context.
NSManagedObjectContext *addingContext = [[NSManagedObjectContext alloc] init];
self.addingManagedObjectContext = addingContext;
[addingContext release];
[addingManagedObjectContext setPersistentStoreCoordinator:[[fetchedResultsController managedObjectContext] persistentStoreCoordinator]];
Cocktail *newCocktail = (Cocktail *)[NSEntityDescription insertNewObjectForEntityForName:@"Cocktail" inManagedObjectContext:self.addingManagedObjectContext];
newCocktail.baseLiquor = (Ingredient *)[NSEntityDescription insertNewObjectForEntityForName:@"Ingredient" inManagedObjectContext:self.addingManagedObjectContext];
newCocktail.mixer = (Ingredient *)[NSEntityDescription insertNewObjectForEntityForName:@"Ingredient" inManagedObjectContext:self.addingManagedObjectContext];
newCocktail.volume = [NSNumber numberWithInt:0];
addViewController.cocktail = newCocktail;
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:addViewController];
[self.navigationController presentModalViewController:navController animated:YES];
[addViewController release];
[navController release];
}
and here's the site of the crash in the Brand
picker (this NSFetchedResultsController
is backed by the app delegate's managed object context:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
cell.accessoryType = UITableViewCellAccessoryCheckmark;
if ([delegate respondsToSelector:@selector(pickerViewController:didFinishWithBrand:forKeyPath:)])
{
[delegate pickerViewController:self
didFinishWithBrand:(Brand *)[fetchedResultsController objectAtIndexPath:indexPath]
forKeyPath:keyPath]; // 'keyPath' is @"baseLiquor.brand" in the crash
}
}
and finally the delegate implementation:
- (void)pickerViewController:(IngredientsPickerViewController *)pickerViewController
didFinishWithBrand:(Brand *)baseEntity
forKeyPath:(NSString *)keyPath
{
// set entity
[cocktail setValue:ingredient forKeyPath:keyPath];
// Save the changes.
NSError *error;
if (![cocktail.managedObjectContext save:&error]) {
// Update to handle the error appropriately.
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
exit(-1); // Fail
}
// dismiss picker
[self.navigationController popViewControllerAnimated:YES]
}
EVEN MORE
I'm making progess based on Marcus' suggestions -- I mapped the addingManagedObjectContexts
to the parent managedObjectContext and wrapped everything in begin/endUndoGrouping
to handle cancel vs. save.
However, the object to be created is in an NSFetchedResultsController
, so when the user hits the "+" button to add the Cocktail
, the (possibly-to-be-undone) entity briefly appears in the table view as the modal add view controller is presented. The MDN example is Mac-based so it doesn't touch on this UI behavior. What can I do to avoid this?