0
votes

Like in a lot of apps, I have a list of items (populated by a Core Data fetch request), a sheet to create new items, and a sheet to edit an item when tapping on a row in my list. I'm trying to unify both forms to create and edit an update, and put the cancel / save logic in a superview of the form.

So I've something like this:

  • ListView: a list with row populated by a Core Data fetch request
  • AddView: a NavigationView with the FormView embed + cancel and save button
  • EditView: a NavigationView with the FormView embed + cancel and save button
  • FormView: a TextField to update the name of the item

In the init() for the AddView, I create a new NSManagedObject without any context (I do that because I don't want my ListView to be updated when I create a new item in the AddView, but only when I save this item -> alternative could be to use a child context, or filter the fetch request results based on the isInserted or objectID.isTemporaryID of the return objects). AddView contains a NavigationView with the FormView embed, a cancel button, and a save button. This save button is disabled based on a computed property on the managed object (name for the object can't be nil).

In the EditView, I pass the item that was tapped from the ListView. This item is an existing NSManagedObject attached to the main viewContext of the app (coming from the fetch request of the ListView). EditView contains a NavigationView with the FormView embed, a cancel button and a save button (exactly like the AddView). This save button is also disabled based on the same computed property.

My issue is that when I update the name of the item from the TextField in my FormView, the condition to enable / disable the save button is not working for the AddView (this AddView is actually not refreshed when I change the item name from the FormView) but working for the EditView (this EditView is refreshed when I change the item name from the FormView). If I attach a context to the new NSManagedObject in the init() of the AddView, the condition is working like in the EditView.

So it appears that a NSManagedObject without any context is not observed by SwiftUI? Am I missing anything or is that a bug?

2
NSManagedObject is invalid w/o context, so I don't see any bug here. - Asperi
Ok. What would be the most elegant way to achieve what I’m trying to do? I initially started to work with temporary structs to act as proxy for the NSMamagedObject and to create the NSManagedObject based on the struct but thought about only using MO instead of having this conversion. - alpennec

2 Answers

0
votes

I wouldn't be surprised (but haven't verified) if the change-notifying ability of a managed object depends on the presence of the context. I can't think of a situation where you'd want to create a managed object without a context.

You should use a child context. The context does a lot of work for you in Core Data (managing relationships, probably change notifying, validation etc), and offers a simple way to cancel / save changes - just save the child context and the data flows back up into the main context, or discard the context to abandon.

0
votes

A workaround to get the change notifications is to add this override to the NSManagedObject subclass:

override public func willChangeValue(forKey key: String) {
    super.willChangeValue(forKey: key)
    self.objectWillChange.send()
}

NSManagedObject could be subclassed to add this override (more info here and here)


We can be more specific on the update value if we don't want to trigger the change for every key. This will also work for relationships (not the case for the above solution).

func setName(_ name: String) {
    objectWillChange.send()
    self.name = name
}

In this case, my AddView updates even if the observed object does not have a context (change notifications are probably trigger only when a context exists for the object). The save button is disabled / enabled based on the following computed property in my NSManagedObject subclass.

var canBeSaved: Bool {
    if self.name.isEmpty {
       return false
    } else {
       return true
    }
}