I have a configuration view, with several toggle buttons that need to save its state to disk whenever the user clicks any of them. To simplify the problem, I've just created an example project with a single Toggle:
struct MyApp: App {
@State var session = AppSession()
var body: some Scene {
WindowGroup {
ContentView()
.environment(session)
}
}
}
@Observable public class AppSession {
public var value: Bool = true
}
struct ContentView: View {
@Environment(AppSession.self) private var session: AppSession
@State private var enableToggle: Bool = false //this value is set here to avoid having to make the property optional, but its initial value is set on the onAppear.
var body: some View {
VStack {
Toggle("Test Toggle", isOn: $enableToggle)
}
.onAppear {
print("onAppear")
self.enableToggle = session.value
}
.onChange(of: enableToggle) { oldValue, newValue in
print("Changed oldValue: \(oldValue) newValue: \(newValue)")
//saveConfigurationToDisk()
}
}
}
The problem I'm facing is that the onChange event is triggered the moment the view is rendered and before the user taps on the toggle, causing an unnecessary save of the state to disk, and this happens because on the onAppear we are setting the initial value for the property (which cannot be set on a custom init because it comes from an environment object).
I can start adding variables to track the first execution of the onChange to be able to ignore it, but I have the feeling that there has to be a better way, a more swiftui one.
Is there any other way to initialize properties whose value comes from the environment so that the onChange is not trigered until they have been initialized?
Try this different approach using the data model directly with @Bindable
, such as:
struct ContentView: View {
@Environment(AppSession.self) private var session: AppSession
var body: some View {
@Bindable var session = session // <--- here
VStack {
Toggle("Test Toggle", isOn: $session.value) // <--- here
}
.onChange(of: session.value) { oldValue, newValue in
print("----> onChange oldValue: \(oldValue) newValue: \(newValue)")
//saveConfigurationToDisk()
}
}
}
This approach IMHO answers, How to initialize a property ...
, it is initialised in the data model,
and ... so that the onChange does not track its change of value until properly initialized
, only the changes are tracked and actioned in onChange
but not the initial value setting.
It is another way of achieving what you want without the intermediate enableToggle
.