-1

I'm using Core Data to persist data in my app, and have a question about mixing parent and child managed object contexts down a view hierarchy.

For simplicity, let's say my app allows users to create a library of recipes they've come up with.

In the app, I have three screens:

  1. LIBRARY - The top level 'library' view where the user can select from different recipes, and tap to edit them or add a new recipe.
  2. EDIT/ADD MODAL - The edit/add screen which allows an existing recipe to be changed, or a new one created from scratch.
  3. INGREDIENTS POPOVER - A selection popover which allows the user to select from a list of ingredients, and add new ones to the list.

As seems to be common practise, I'm creating a child context for the edit/add modal view so that I can discard the changes if the user taps 'cancel', and only persist them if 'save' is tapped.

My uncertainty is around how to handle the ingredients popover. I would like for the ingredients in this list to be shared between all recipes, so that when the user adds an ingredient via the edit/add modal for one recipe, they can later add it to other recipes without having to add it to the list again. Of course, if I create new ingredient instances in the child context, they will be discarded if the user taps cancel on the edit/add modal. Therefore, I'm currently attempting to use the parent context.

Unsurprisingly, I'm currently seeing the following error when I tap to select an ingredient for the current recipe:

Illegal attempt to establish a relationship 'ingredient' between objects in different contexts

So my question is, how can I use a child context for the edit/add modal, but have the ingredients popover use the parent context so that changes made in this view are persisted, no matter whether the user saves or cancels in the edit/add modal?

Happy to include some code if needed, but mainly a best practices question. Code examples of solutions would be appreciated.

  • 1
    You might use `object(with objectID:)`, to find the the entity in the correct context IF it's inside the persistentStore. Meaning, that if it's only in RAM, it will fail. – Larme Jan 06 '22 at 17:24
  • @Larme I think I understand what you're getting at. So once I've persisted the ingredient using the parent context, use `object(with objectID:)` to retrieve the same entity from the child context? I've posted some code that seems to be working (although needs more testing). Is this what you were suggesting? – Grant_Rodgers Jan 06 '22 at 17:52
  • Yes, that's what I'm suggesting. It's better than doing a specific fetchrequest to fetch the entity in your child context. – Larme Jan 06 '22 at 17:52
  • Awesome, good stuff. Thanks for the pointer! – Grant_Rodgers Jan 06 '22 at 18:13

1 Answers1

0

Based on the comment by @Larme, I think I have a solution:

I added automaticallyMergesChangesFromParent to the child context, so that when the new ingredient is added to the parent context, it becomes available to the child context also.

Then, when the user taps on an ingredient, from within the popover, I retrieve the same ingredient they tapped from within the child context, and assign it to a property which is bound back to the edit/add modal:

@Environment(\.managedObjectContext) private var managedObjectContext // Child context
@Binding var selectedIngredient: Ingredient?

...

// `generalSettings` holds data which was loaded from the parent context
ForEach(self.generalSettings.ingredients) { ingredient in
    VStack {
        Button(action: {
            if let ingredientInChildContext = self.managedObjectContext.object(with: ingredient.objectID) as? Ingredient {
                self.selectedIngredient = ingredientInChildContext
            }

            self.presentationMode.wrappedValue.dismiss()
        }, label: {
            self.buttonLabelForIngredient(ingredient)
        })
    }
}