WHAT I NEED: I've a stepper to set a value. This value is completely different if user is using the app with different units.
WHAT'S THE PROBLEM: While I just play with the unit picker in the settings tab, the value (stored as @AppStorage in the settings tab) in the main tab updates correctly. When I touch the stepper and modify the value in the main tab, if I try to modify the unit in the settings tab, everything mess up! (While the value in the settings tab keeps updating correctly, the value displayed in the main tab doesn't).
SETTING TAB: class
Defaults: ObservableObject {
@AppStorage("appUnits") var appUnits: String = "πͺπΊ"
@AppStorage("memorizedDensity") var density: Double = 0.794
}
struct settings: View {
@ObservedObject var defaults = Defaults()
var body: some View {
List {
Section {
HStack {
ViewThatFits {
Text("App default Units")
}
Spacer(minLength: 10)
Picker(selection: $defaults.appUnits, label: Text("Unit")) {
ForEach(["πͺπΊ", "πΊπΈ"], id: \.self) {riga in
Text(riga)
}
}
.onChange(of: defaults.appUnits) {newValue in
if newValue == "πͺπΊ" {
defaults.density = 0.793
} else if newValue == "πΊπΈ" {
defaults.density = 6.66
}
// I need to set a different value when the unit changes
print(defaults.density)
}
.frame(width: 120)
.pickerStyle(.segmented)
}
}
}
}
}
MAIN TAB:
struct stepper: View {
@ObservedObject var defaults = Defaults()
var body: some View { fuelDatas }
var fuelDatas: some View {
VStack {
Text("Fuel density")
HStack {
Text(defaults.density, format: .number.precision(.fractionLength(defaults.appUnits == "πͺπΊ" ? 3 : 2)))
Text(defaults.appUnits == "πͺπΊ" ? "g/ml" : "lb/US gal")
}
Stepper("", value: defaults.$density, in: defaults.appUnits == "πͺπΊ" ? 0.750...0.850 : 6...7, step: defaults.appUnits == "πͺπΊ" ? 0.001 : 0.01)
.frame(width: 50.0)
}
}
}
I know I'm messing something with the binding stuff and I'm probably creating 2 sources of truth, but I can't find what's wrong in the code! Here's the sample project-
Ideally, you should just declare the @AppStorage
s in your View
s, instead of wrapping them in another Defaults
class.
struct stepper: View {
@AppStorage("appUnits") var appUnits: String = "πͺπΊ"
@AppStorage("memorizedDensity") var density: Double = 0.794
...
}
Your current actually works if you take the two views out of the TabView
, so it seems like this is another one of those peculiarities that TabView
has.
If you really want a Defaults
object for some reason, one way to make this work is to make sure all your views use the same object.
struct settings: View {
// do not initialise it here
@ObservedObject var defaults: Defaults
...
}
struct stepper: View {
// do not initialise it here
@ObservedObject var defaults: Defaults
...
}
In your tab view, declare a @StateObject
and pass it down to both the stepper and settings.
@StateObject var defaults = Defaults()
var body: some View {
TabView {
NavigationStack {
stepper(defaults: defaults)
}
.tabItem { Label("stepper", systemImage: "1.circle") }
NavigationStack {
settings(defaults: defaults)
}
.tabItem { Label("setting", systemImage: "2.circle") }
}
}
You can also pass it as an @EnvironmentObject
,
NavigationStack {
stepper()
}
.environmentObject(defaults)
...
struct stepper: View {
@EnvironmentObject var defaults: Defaults
...
}