NEW: My new book Pro SwiftUI is out now – level up your SwiftUI skills today! >>

SOLVED: How do I create a new CoreData object without referencing an empty object first?

Forums > Swift

Hello,

This might be super obvious but I'm new to the whole iOS ecosystem and I'm having a bit of trouble figuring something out.

I'm creating a simple app that will allow me to create recipes, and with that I need to be able to add ingredients to recipes.

I have a View that will allow me to create a new recipe, however I don't know how to initialize that view without already having a recipe to reference.

I have tried passing an empty recipe through to the CreateRecipeView and that works until I need to be able to reference the actual CoreData object when trying to add an Ingredient.

I'll post some code down below and perhaps I can get pointed in the right direction - I feel as though I may be close but there is just something that I don't understand.

HomeView

NavigationLink {
                CreateRecipeView(recipe: Recipe())
            } label: {
                Text("Create Recipe")
            }

CreateRecipeView

struct CreateRecipeView: View {
    var recipe: Recipe
    @EnvironmentObject var dataController: DataController
    @Environment(\.managedObjectContext) var managedObjectContext
    @Environment(\.dismiss) var dismiss

    @State private var title = ""
    @State private var detail = ""
    @State private var cookingTimeMinutes = ""
    @State private var cuisine = ""
    @State private var meal = ""
    @State private var difficulty = ""

    static let cuisines = ["American", "Indian", "Mexican", "Italian", "French", "Japanese", "Korean", "Peruvian", "Thai", "German"]
    static let meals = ["Breakfast", "Lunch", "Dinner", "Dessert", "Snack"]
    static let difficulty = ["Easy", "Medium", "Hard"]

    init(recipe: Recipe) {
        self.recipe = recipe
    }
    func update(recipe: Recipe) {
        recipe.title = title
        recipe.detail = detail
        recipe.cookingTimeMinutes = Int16(cookingTimeMinutes) ?? 20
        recipe.cuisine = cuisine
        recipe.meal = meal
        recipe.difficulty = difficulty
        dataController.save()

    }

And then further down...

NavigationLink {
                    CreateIngredientView(recipe: recipe)
                } label: {
                    Text("Add Ingredient")
                }

This will not work because of the empty object that I passed in.

However, I don't know what else I can do - it needs to accept a recipe to move forward. I've tried making the Recipe optional and going about it that way but the call to CreateRecipeView still wants a recipe and I don't know what to do.

Thanks for any assistance.

   

For creating a CoreData entity object you must have the managedObjectContext. Otherwise, it doesn't work. So for example: Recipe(context: managedObjectContext)

What you could do is creating the Recipe object in your init method of Recipe.

   

hi Nathan,

to start, i think you'd find it easier to use a sheet to open up a "add new Recipe" view, rather than navigate to it. that allows you to either "save" or "cancel," where:

  • "save" means create a new Recipe object in Core Data, copy the data from all the @State variables over to the Recipe object, save the Core Data context, and then dismiss the view.
  • "cancel" means dismiss the sheet (no Recipe is created).

as for then navigating from an "add new Recipe" to an CreateIngredientView, there's no need to pass a fully-formed Recipe ... assuming you want this view to manipulate a list of ingredients and return to CreateRecipeView, you would just need to pass along, say, a simple array of structs that hold the data that you will eventually move to Core Data. so something like:

CreateIngredientView(ingredients: $ingredients)

where CreateRecipeView has its own

@State private var ingredients = [DraftIngredient]()

and you have defined an in-memory DraftIngredient that looks something like

struct DraftIngredient {
  var name: String
  var amount: Double
  // other data as well?
}

this would mean expanding the "save" operation in CreateRecipeView to be

  • create a new Recipe object in Core Data; copy the data from all the @State variables over to the Recipe object; for each of the draftIngredient items, create an Ingredient object in Core Data and copy over the data from the draftIngredient; link the Ingredient to the Recipe; save the Core Data context; and then dismiss the view.

i think i got all the basics above. if you'd like to take a look at some code where i went through this some time ago, take a look at my ShoppingList15 repo on Github, where i use the language of "draft" or "in memory" strcuts/objects quite a bit to collect data first, and only commit those data to Core Data objects once the user says "save."

one final idea: you might consider adding ingredients to a recipe withing the CreateRecipeView itself.

hope this helps,

DMG

   

Thank you for the comprehensive suggestion @delawaremathguy! I am going to try this out, it sounds a lot less troublesome than trying to pass contexts around all over the place.

   

Nathan,

you'll still have to pass around a managedObjectContext. you need this because it's your window, or "hook," into Core Data. you need this to be able to create new (and delete existing) objects.

if your "main view" provides an array of Recipe objects and you use a @FetchRequest, then you probably have the managedObjectContext already in its environment (@FetchRequest depends on this).

if you open a sheet for an "add new recipe" view, then you probably want to pass that context into the sheet (sheets don't automatically inherit the environment of the presenting view), and retrieve it. so something like this seems normal for most code out there these days: put the managedObjectContext's into the sheet's environment directly:

.sheet (isPresented: ... ) {
  CreateRecipeView()
     .environment(\.managedObjectContext, managedObjectContext) // the presenting view's managedObjectContext variable
} 

and then retrieve that managedObjectContext in the "add new recipe" view, as you already have it in your code:

struct CreateRecipeView: View {
  @Environment(\.managedObjectContext) var managedObjectContext

  // and lots more stuff as you have it above
}

good luck, and we hope to hear more from you in the future.

regards,

DMG

   

Hi @delawaremathguy,

Thank you for the tip about passing the MOC thru the wrapper on the sheet! I don't think I would have figured that out myself. I took your advice on using the sheet and creating a DraftIngredient struct to hold temp values and then assigning them to an Ingredient on save and that worked beautifully!

I did decide to create ingredients and add them in the same page as recipes as that felt pretty good from a UX perspective and I think it was easier on me to conceptualize and execute.

I also did check out your ShoppingList15 repo which helped me with some ideas about what to do in the future layout for my application as their business domains are quite similar.

Thank you for all of the patience and help that you have provided! I suspect I will be asking you more questions in the future. :)

Nathan

   

Hacking with Swift is sponsored by MadMachine

SPONSORED Want to explore your Swift skill outside of the Apple world? Join the MadMachine community and start to program microcontrollers in Swift.

Get it now

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.