Problem Statement
I'm having trouble understanding how to reset a @State property to its initial value.In my app, I have one view that sets an integer "default value". The other view reads it, but doesn't change it:
Code Block struct ContentView: View { @State var selectedValue = 1 var body: some View { VStack{ DefaultValueView(defaultValue: $selectedValue) ComputeView(defaultValue: selectedValue) } } }
The first view is simple: it uses a @Binding to the selected value and updates it based on the user's selection. Here, we let the user choose a default value between 1 and 10:
Code Block struct DefaultValueView: View { @Binding var defaultValue: Int var body: some View { VStack { Text("Select the default value") ForEach(1...10, id: \.self) { i in Button("Value: \(i)") { defaultValue = i }.background(i == defaultValue ? Color.red : Color.clear) } } } }
In a second, I want to have the following behavior:
When the selectedValue changes, it displays the selected value.
The user can increment or decrement the displayed value. This doesn't change the selectedValue, just what's displayed in this view.
Code Block struct ComputeView: View { @State var additionalValue: Int init(defaultValue: Int) { _additionalValue = State(initialValue: defaultValue) } var body: some View { VStack { Text("10 plus..") Stepper("\(additionalValue)") { additionalValue += 1 } onDecrement: { additionalValue -= 1 } Text("= \(10 + additionalValue)") }.padding() } }
This achieves the 2nd behavior (I can edit the displayed value with the stepper) but not the 1st. When I change "default value" to something else, it doesn't change in this view. I've got a breakpoint in the init method, so I know it's being re-set to the new initialValue, but it doesn't change.
What am I missing?
Full app to copy/paste:
Code Block import SwiftUI @main struct DefaultEditableValueApp: App { var body: some Scene { WindowGroup { ContentView() } } } struct ContentView: View { @State var selectedValue = 1 var body: some View { VStack{ DefaultValueView(defaultValue: $selectedValue) ComputeView(defaultValue: selectedValue) } } } struct DefaultValueView: View { @Binding var defaultValue: Int var body: some View { VStack { Text("Select the default value") ForEach(1...10, id: \.self) { i in Button("Value: \(i)") { defaultValue = i }.background(i == defaultValue ? Color.red : Color.clear) } } } } struct ComputeView: View { @State var additionalValue: Int init(defaultValue: Int) { _additionalValue = State(initialValue: defaultValue) } var body: some View { VStack { Text("10 plus..") Stepper("\(additionalValue)") { additionalValue += 1 } onDecrement: { additionalValue -= 1 } Text("= \(10 + additionalValue)") }.padding() } }
What you described is the expected behavior of @State in SwiftUI.
The struct State initially does not have a storage for the state, SwiftUI allocates it when a View is established. (Not when it is initialized.)
The initializer of a SwiftUI View is called at any time SwiftUI needs it, and if the storage for the state is already allocated, SwiftUI uses the already allocated storage keeping the value. In such cases, initialValue is simply ignored.
Thus, init is not a good place to update the value of @State vars.
If you want to detect the selectedValue changes, you need to explicitly implement some mechanisms to detect the changes.
An example:
The struct State initially does not have a storage for the state, SwiftUI allocates it when a View is established. (Not when it is initialized.)
The initializer of a SwiftUI View is called at any time SwiftUI needs it, and if the storage for the state is already allocated, SwiftUI uses the already allocated storage keeping the value. In such cases, initialValue is simply ignored.
Thus, init is not a good place to update the value of @State vars.
If you want to detect the selectedValue changes, you need to explicitly implement some mechanisms to detect the changes.
An example:
Code Block import SwiftUI @main struct DefaultEditableValueApp: App { var body: some Scene { WindowGroup { ContentView() } } } class SelectedState: ObservableObject { @Published var value: Int = 1 } struct ContentView: View { @StateObject var selectedState = SelectedState() var body: some View { VStack{ DefaultValueView(defaultValue: $selectedState.value) ComputeView(selectedState: selectedState) } } } struct DefaultValueView: View { @Binding var defaultValue: Int var body: some View { VStack { Text("Select the default value") ForEach(1...10, id: \.self) { i in Button("Value: \(i)") { defaultValue = i }.background(i == defaultValue ? Color.red : Color.clear) } } } } struct ComputeView: View { @State var additionalValue: Int = 0 @ObservedObject var selectedState: SelectedState var body: some View { VStack { Text("10 plus..") Stepper("\(additionalValue)") { additionalValue += 1 } onDecrement: { additionalValue -= 1 } Text("= \(10 + additionalValue)") }.padding() .onReceive(selectedState.$value) {value in additionalValue = value } } }