I'm new to programming and if someone could provide a basic explanation that would be sincerely appreciated.
I am trying to pass an NSManagedObject that I have fetched and is associated with the main view context to a child context for editing. However, nothing is showing up in my detail view. For some reason the managed object isn't being passed.
// Content View, everything is showing up fine here
struct ContentView: View {
let persistenceController = PersistenceController.shared
@Environment(\.managedObjectContext) private var viewContext
@StateObject var viewModel = ContentViewModel()
@FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \Person.name, ascending: true)],
animation: .default)
private var people: FetchedResults<Person>
var body: some View {
NavigationView {
List {
ForEach(people) { person in
NavigationLink(destination: EditView(viewModel: EditViewModel(persistenceController: persistenceController, person: person))) {
Text(person.name ?? "Unknown")
Text("\(person.objectID)")
}
}
}
// EditViewModel
struct EditViewModel {
var person: Person
let context: NSManagedObjectContext
let persistenceController: PersistenceController
init(persistenceController: PersistenceController, person: Person) {
self.context = persistenceController.childViewContext()
self.person = (persistenceController.childViewContext().object(with: person.objectID) as? Person)!
self.persistenceController = persistenceController
}
// EditView - all person properties are nil
struct EditView: View {
@State var viewModel: EditViewModel
var body: some View {
GeometryReader { proxy in
ScrollView(.vertical) {
VStack {
TextField("Name", text: $viewModel.person.name ?? "")
Button("print") {
print(viewModel.person.name)
}
}
Please let me know if I have failed to provide enough information to produce a minimum reproducible example and I will add more.
Thanks in advance.
CodePudding user response:
Since you are creating and injecting the view model into your edit view from your content view you don't need to use/pass your PersistenceController class and also since you are passing a Person object we don't need a NSManagedObjectContext either since the Person object has a reference to its context.
So I would rewrite the view model like this
struct EditViewModel {
var person: Person
let context: NSManagedObjectContext?
let parentContext: NSManagedObjectContext
init(person: Person) {
guard let parentContext = person.managedObjectContext else {
//no context is really bad and should never happen
FatalError("Found a NSManagedObject that doesn't belong to a context")
self.parentContext = parentContext
let childContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
childContext.parent = parentContext
guard let childPersopn = try? childContext.existingObject(with: person.objectID)) else {
self.childContext = nil
self.person = person
return
}
self.context = childContext
self.person = childPerson
}
//...
}
I find it a bit strange that the view model is a struct and not a class. You could make it into a class that conforms to ObservableObject
and declare it as a @StateObject
instead in your view.