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
- injected
.navigationDestination(for: Pokemon.self, destination: { pokemon in
PokemonDetail()
.environmentObject(pokemon) // << here !!
})
- used
struct PokemonDetail: View {
@EnvironmentObject var pokemon: Pokemon // << here !!
var body: some View {
// ...
- 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
Cropping Cgrect from Avcapturephotooutput (Resizeaspectfill)
Swift: Strange Behavior About Unwrapping
Comma Automatically Being Added to Textfield in Swift
Macos App Sandboxing - Read Access to Referenced Files from Parsed Xml
Swift Compile Error, Subclassing Nsvalue, Using Super.Init(Nonretainedobject:)
Parse Weird Bug in Swift That Causes Acl Write Permissions to Change to an Objectid
How to Create Rounded Image with Border and Shadow as Mkannotationview in Swift
Filter, Closure, Functional Syntax Version of for Loop with Multiple Conditions
Reactive Cocoa/Reactive Swift - Swift 3.0 Missing Methods
Why Is This Predicate Format Being Turned into '= Nil'
Cannot Use Mutating Member ... Because Append
Swift - Connect Delegate to Custom Xib Cell
Understanding Optional Global Variables in Swift
Applying Impulses in Spritekit
Dictionary Becomes Nil When Trying to Pass Back Cllocation Object to iOS App Extension
Core Data Appears to Lose Data After Xcode Upgrade
How to Get Address in English Language Only Using Gmsgeocoder