I have an application based on the CoreDataBooks example that uses addingManagedObjectContext to add an Ingredient to a Cocktail to undo the whole add. CocktailsDetailViewController , in turn, calls BrandPickerViewController to (optional), setting the brand name for this ingredient. Cocktail , Ingredient and Brand are all NSManagedObjects . Cocktail requires at least one Ingredient ( baseLiquor ) to be baseLiquor , so I create it when Cocktail is created.
If I add a Cocktail to the CocktailsAddViewController : CocktailsDetailViewController (merging into the Cocktail managed object context when saved) without setting baseLiquor.brand , then it works to set the Brand from the collector (also stored in the Cocktails managed context) later from the CocktailsDetailViewController .
However, if I try to set baseLiquor.brand in CocktailsAddViewController , I get:
Application termination due to an uncaught exception "NSInvalidArgumentException", reason: "Illegal attempt to create a brand relationship between objects in different contexts
From this question, I understand that the problem is that the Brand is stored in the managedObjectContext application, and the recently added Ingredient and Cocktail stored in addingManagedObjectContext , and that passing the ObjectID instead will prevent a crash.
I donβt understand how to implement the collector in the general case, so that all ingredients ( baseLiquor , mixer , garnish , etc.) can be set at the time of adding, one by one from the CocktailsDetailViewController after creating the Cocktail . In other words, following the CoreDataBooks example, where and when would an ObjectID be converted to an NSManagedObject from the parent MOC in cases of adding and editing? IPD
UPDATE . Here's the method added:
- (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 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 is the NSFetchedResultsController site of Brand (this NSFetchedResultsController maintained by the application 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];
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 doing a progess based on Marcus's suggestions - I matched addingManagedObjectContexts to the parent managed ObjectContext and wrapped everything in begin/endUndoGrouping to handle undo and save.
However, the object to be created is located in the NSFetchedResultsController , so when the user clicks the β+β button to add a Cocktail , the object will briefly appear in the table (possibly to be destroyed) as shown as a modal view submission controller. The MDN example is Mac-based, so it does not affect the behavior of this user interface. What can I do to avoid this?