swifty
swifty

Reputation: 1151

SwiftUI Modal Environment

I don't really understand how the environment works when using modals. It looks like the environment is separated for modal views.

I made a quick example using core data and saving an object to Core Data. If I do not pass the environment, then the object does not get saved and I get the following error when trying to save the object: The operation couldn’t be completed. (Foundation._GenericObjCError error 0.)

If I pass the managed object context in the modal's environment it works. I commented out the line that makes it work.

Can anyone explain why does this happen, please?


    @FetchRequest(fetchRequest: ToDoItem.fetchAllItems()) var items
    @Environment(\.managedObjectContext) var managedObjectContext
    @State var showAddModal = false


    var body: some View {

        VStack {

            List(items, id: \.name) { item in
                Text(item.name)
            }

            Button(action: {
                self.showAddModal.toggle()
            }) {
                Text("Add item")
            }.sheet(isPresented: $showAddModal) {
                ModalView()
//                    .environment(\.managedObjectContext, self.managedObjectContext)
//                It works if the managed object context is passed in the modal's environment
            }
        }
    }
}

struct ModalView: View {

    @State var toDoItemName: String = ""
    @Environment(\.presentationMode) var presentationMode
    @Environment(\.managedObjectContext) var managedObjectContext

    var body: some View {

        VStack {
            TextField("Item name", text: $toDoItemName)
            Button(action: {
                let toDoItem = ToDoItem(context: self.managedObjectContext)
                toDoItem.name = self.toDoItemName
                do {
                    try self.managedObjectContext.save()
                } catch {
                    print(error.localizedDescription)
                }
                self.presentationMode.wrappedValue.dismiss()
            }) {
                Text("Add")
            }
        }
    }
}```

Upvotes: 5

Views: 1554

Answers (5)

arsenius
arsenius

Reputation: 13246

Unfortunately, this is still an issue for SwiftUI views presented via UIKit (at least iOS 15.0-17.0). However, you can at least pass the existing environment in manually. Future updates won't get passed through, but those should be rare when a view is presented.

@Environment(\.self) var fullEnvironment // Define this in a view somewhere and pass it along to your presenting UIViewController

let vc = UIHostingController(rootView: MyView().environment(\.self, self.fullEnvironment)) // Pass it in.

Upvotes: 0

zdravko zdravkin
zdravko zdravkin

Reputation: 2378

when you pass view with .sheet, environment objects are not passed so you need to specified your environment object.

Try to open with ModalView() with NavigationLink and everything will work.

Upvotes: 0

dRod
dRod

Reputation: 11

If I can tag on to this:

when I do add the .environment(\.managedObjectContext, self.managedObjectContext) My ObservableObject class that I create in the child (modal) will not work correctly. All vars in the class reset to their initial value each time a call is made to the class's properties or methods.

Upvotes: 0

Joshua Woodward
Joshua Woodward

Reputation: 41

It should pass through automatically but doesn't.

"if view A presents view B as a sheet then they don’t automatically share environment data, and we need to send it in by hand. Apple has described this sheet situation as a bug that they want to fix, so I’m hopeful this will change in a future update to SwiftUI." - Paul Hudson - Hacking With Swift

I don't know his sources, but he seems to know what he is talking about normally.

Upvotes: 4

Dragos
Dragos

Reputation: 1040

When you create the first view in SwiftUI, the framework generates an Environment for it. SwiftUI uses Environment to store different system settings like the color scheme, managedObjectContext and app-specific stuff like default font. All system settings and app-specific stuff except our custom EnvironmentObjects are pre-defined environment values(here, you can check the full collection of environmental values). One important thing to remember is that each view inherits Environment from its parent.

So with the following line of code in your ModalView, you are getting the managedObjectContext from the Environment of your ModalView that was inherited from the view that presents it.

@Environment(\.managedObjectContext) var managedObjectContext

Also, when you work with Coredata, you should pay attention to contexts, especially if you use multiple contexts.

Tip: If you go to our root view and set all the values that you might need, you will propagate them through your entire app (as values injected in Environment).

References:

Upvotes: 0

Related Questions