Search code examples
core-dataswiftui

I create an object in a view, how do I pass this object to child views as an ObservedObject?


I am trying to create a Recpipe object (CoreData entity) and then pass that object to a ChildView after it's created. What's the best way to do this?

My first attempt was to create a @StateObject in the MainView and then pass that to ChildViews as an @ObservedObject, but this seems to only work great for objects that already exist. The @StateObject is a 'get only' property so I can't modify it based on the function return. Perhaps I don't really want to create a @StateObject in this circumstance?

struct MainView: View {
    
    @State private var presentChildView: Bool = false
    @StateObject private var recipe: Recipe = Recipe()

    var body: some View {
        VStack {
            
            NavigationLink(destination: ChildView(recipe: recipe), isActive: $presentChildView) {
                EmptyView()
            }

            Button("Action", action: {
                recipe = functionThatReturnsRecipe()
                presentChildView = true
            })
        }
    }
}

struct ChildView: View {
    @ObservedObject private var recipe: Recipe
    
    var body: some View {
        Text(recipe.title)
    }
}

Solution

  • Normally you hold the object in @State or even better inside a struct to hold all the ChildView's related vars so it can be tested independently, e.g.

    struct ChildViewConfig {
        var recipe: Recipe?
        var isPresented = false
        
        mutating func present(viewContext: NSManagedObjectContext) {
            recipe = Recipe(context: viewContext)
            isPresented = true
        }
    
        // might have other save or dismiss mutating funcs here.
    }
    
    struct MainView: View {
        @Environment(\.managedObjectContext) private var viewContext
        @State private var config = ChildViewConfig()
        
        var body: some View {
            VStack {
                Button("Present") {
                    config.present(context: viewContext)
                }
            }
            .sheet(isPresented: $config.isPresented, onDismiss: nil) {
                ChildView(recipe: config.recipe!)
            }
        }
    }
    
    struct ChildView: View {
        @ObservedObject private var recipe: Recipe
        
        var body: some View {
            Text(recipe.title)
        }
    }
    

    This answer to another question uses the more advanced .sheet(item:onDismiss) that you may prefer because is designed for this use case of editing an item and allows for the use of a child context scratch pad because when setting the item to nil means the item.childContext will be discarded if the editing in the sheet is cancelled.

    Learn more about this @State Config struct pattern at 4:18 in Data Essentials in SwiftUI (WWDC 2020)