Context
I have a pretty simple Form
using the MVVM
architecture. However, I encountered a problem with this implementation. My DetailView
has a DummyState
that changes the corresponding attribute of Entity
onChange
.
Problem: Once the
DetailView
appears, bothToggles
are on which is as expected. However, when turning the localToggle
off, the otherToggle
remains on, which is unexpected. However, as soon as I interact with another element (not inMRE
), the secondToggle
seems to refresh and turns off.
Code
@objc(Entity) public class Entity: NSManagedObject {
@NSManaged public var enabled: Bool
}
class FormViewModel: ObservableObject, CustomFormObservable {
@Published var entity = Entity(context: ...)
init() { self.entity.enabled = true }
var isValid: Bool { self.entity.enabled }
}
struct FormView: View {
@StateObject private var formVM = FormViewModel()
var body: some View {
CustomForm(formVM: formVM) {
DetailView(entity: $formVM.entity)
}
}
}
struct DetailView: View {
@Binding var entity: Entity
@State private var localEnabled: Bool = true
var body: some View {
Toggle("Local Toggle", isOn: $localEnabled)
.onChange(of: localEnabled) { entity.enabled = $0 }
Toggle("Toggle", isOn: $entity.enabled)
}
}
Question
Because NSManagedObject
is reference type.
There is a simple rule:
@State
and @Binding
is for value types like structs.@StateObject
and @ObservedObject
is for reference types like classes.And the dollar-sign reference is also only for value types.
Fortunately NSManagedObject
conforms to ObservableObject
by default.
struct MainView: View {
@StateObject private var formVM = FormViewModel()
var body: some View {
DetailView(entity: formVM.entity)
}
}
struct DetailView: View {
@ObservedObject var entity: Entity
@State private var localEnabled: Bool = true
var body: some View {
Toggle("Local Toggle", isOn: $localEnabled)
.onChange(of: localEnabled) { entity.enabled = $0 }
Toggle("Toggle", isOn: $entity.enabled)
}
}