Why Does Core Data Context Object Have to Be Passed via Environment Variable

Why does Core Data context object have to be passed via environment variable?

why cannot it be passed via View's property? What's the advantage of doing so?

It can. Just it is used by other wrappers like @FetchRequest from environment, but nobody stops you to combine them, because context is an object of reference type, so you can pass its reference anyway you want.

So the following is absolutely valid:

let contentView = FlightsEnrouteView(context: context)
.environment(\.managedObjectContext, context)

and

struct FlightsEnrouteView: View {
@EnvironmentObject(\.managedObjectContext) var envContext
var context: NSManagedObjectContext
}

How to pass/get Core Data context in SwiftUI MVVM ViewModel?

Here is your scenario

let contentView = MainView(context: context)          // << inject
.environment(\.managedObjectContext, context)

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

@ObservedObject private var viewModel: ViewModel // << declare

init(context: NSManagedObjectContext) {
self.viewModel = ViewModel(context: context) // initialize
}
}

How to pass Core Data object from one SwiftUI view to another through environment property

It is possible to do that with .environmentObject modifier, bacause CoreData objects confirm to ObservableObject protocol, so

  1. injected
.navigationDestination(for: Pokemon.self, destination: { pokemon in
PokemonDetail()
.environmentObject(pokemon) // << here !!
})

  1. used
struct PokemonDetail: View {
@EnvironmentObject var pokemon: Pokemon // << here !!

var body: some View {
// ...

  1. in preview as well
struct PokemonDetail_Previews: PreviewProvider {
static var previews: some View {

// ... create preview `pokemon`

return PokemonDetail()
.environmentObject(pokemon) // << here !!
}
}

Environment variable not passed to Subviews

I was able to solve this by fixing up HostingController and guaranteeing the CoreData stack was setup before view construction. First, let's make sure the CoreData stack is ready to go. In ExtensionDelegate:

class ExtensionDelegate: NSObject, WKExtensionDelegate {

let persistentContainer = NSPersistentContainer(name: "Haha")

func applicationDidFinishLaunching() {
persistentContainer.loadPersistentStores(completionHandler: { (storeDescription, error) in
// handle this
})
}
}

I had trouble when this property was lazy so I set it up explicitly. If you run into timing issues, make loadPersistentStores a synchronous call with a semaphore to debug and then figure out how to delay nib instantiation until the closure is called later.

Next, let's fix HostingController, by making a reference to a constant view context. WKHostingController is an object, not a struct. So now we have:

class HostingController: WKHostingController<AnyView> {

private(set) var context: NSManagedObjectContext!

override func awake(withContext context: Any?) {
self.context = (WKExtension.shared().delegate as! ExtensionDelegate).persistentContainer.viewContext
}

override var body: AnyView {
return AnyView(ContentView().environment(\.managedObjectContext, context))
}
}

Now, any subviews should have access to the MOC. The following now works for me:

struct ContentView: View {

@Environment(\.managedObjectContext) var moc: NSManagedObjectContext

var body: some View {
VStack {
Text("\(moc)")
SubView()
}
}
}

struct SubView: View {

@Environment(\.managedObjectContext) var moc: NSManagedObjectContext

var body: some View {
Text("\(moc)")
.foregroundColor(.red)
}
}

You should see the address of the MOC in white above and in red below, without calling .environment on the SubView.

CoreData and SwiftUI: Context in environment is not connected to a persistent store coordinator

Environment values like your moc are automatically passed only to other views in the hierarchy. So if you show a sheet or anything that is not part of your view hierarchy you'll lose the environment and you will need to pass the moc to the new hierarchy, like you did for ContentView. Check this code snippet:

.sheet(isPresented: self.$showSheet) {
SheetView()
.environment(\.managedObjectContext, self.moc)
}

Access environment variable inside global function - SwiftUI + CoreData

Possible approach is to use shared instance, like

class GlobalVariableClass: ObservableObject {
static var shared = GlobalVariableClass() // << here !!

@Published var itemObjects: [NSManagedObject] = []
}

so you can use it

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

var globalVariables = GlobalVariableClass.shared

and

   do {
let results = try managedContext.fetch(fetchRequest)

// make sure to modify itemObjects always in main queue
DispatchQueue.main.async { // << must !!
GlobalVariableClass.shared.itemObjects = results
}
} catch let error as NSError {
print("Could not fetch. \(error), \(error.userInfo)")
}

SwiftUI - passing managed object context to model

You cannot use lazy var in View, because it is immutable. Here is possible approach to solve your case

class ViewModel {
var context: NSManagedObjectContext?
private var model = Model()

init(context: NSManagedObjectContext? = nil) {
self.context = context
}

func checkLogin(username: String, password: String) -> Bool {
return model.checkLogin(username: username, password: password)
}
}

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

private let viewModel = ViewModel() // no context yet here, so just default

//Login form
var body: some View {
Button(action: {
if self.viewModel.checkLogin(username: self.email, password: self.password) {
//allow login
}
}) {
Text("login")
}
.onAppear {
self.viewModel.context = moc // << set up context here
}

}
}

Problem saving data, using CoreData with SwiftUI

Sheets and alerts, etc are modal views. Modal views are normal views, but they are not part of the interface's view hierarchy and are presented on top of the rest of the views on the screen. This means they do not have automatic access to the same @Environment variables that were injected into the interface's view hierarchy (usually in the SceneDelegate or the newer SwiftUI App life-cycle).

To access that same managedObjectContext variable inside the modal View you can pass it along using standard dependency injection.

    }).sheet(isPresented: $showingFlag) {
MyView(context: managedObjectContext)
}

struct MyView: View {
......
var context: NSManagedObectContext!

func setData() {
print(#function)


Related Topics



Leave a reply



Submit