I've been using @AppStorage and UserDefaults for updates in SwiftUI. If I make a change to the vie that has the @AppStorage wrapper all works well. I'm confused with how to make this work globally.
I'm using a struct that has computed properties and formatters associated. The idea is to check user defaults and convert items to lbs or kg. The issue is that the views using the computed properties do not update when UserDefaults is updated. Is there a way to create a global change that would update weightFormatted in SecondaryView below?
// Weight Struct
struct Weight {
var weight: Double
var weightFormatted: String {
return weightDecimalLbsOrKgFormatted2(weight)
}
// Formatting Method
func weightDecimalLbsOrKgFormatted2(_ lbs: Double) -> String {
if (!UserDefaults.standard.bool(forKey: "weightInKilograms")) {
let weightString = decimalFormatterDecimal2(lbs)
return weightString + "lbs"
} else {
let kg = toKg(lbs)
let weightString = decimalFormatterDecimal2(kg)
return weightString + "kg"
}
}
// Where weightInKilograms Is Set
struct AccountView: View {
@AppStorage("weightInKilograms") var weightInKilograms = false
let weight = Weight(weight: 9.0))
var body: some View {
VStack {
Text(weight.weightFormatted)
Toggle(isOn: $weightInKilograms) {
Text("Kilograms")
}
}
}
}
// Secondary View Not Updating
struct SecondaryView: View {
let weight = Weight(weight: 9.0))
var body: some View {
Text(weight.weightFormatted)
}
}
Your problem is that weight
isn't wrapped by any state.
In your AccountView
, give weight
a @State
wrapper:
struct AccountView: View {
@AppStorage("weightInKilograms") var weightInKilograms = false
@State var weight = Weight(weight: 9.0))
var body: some View {
//...
}
}
In SecondaryView
, ensure that weight
is wrapped with @Binding
:
struct SecondaryView: View {
@Binding var weight: Weight
var body: some View {
// ...
}
}
Then, pass weight
as a Binding<Weight>
variable to SecondaryView
within your first View:
SecondaryView(weight: $weight)
Is there a way to create a global change that would update weightFormatted in SecondaryView below?
If you're looking to make a global change, you should consider setting up a global EnvironmentObject
:
class MyGlobalClass: ObservableObject {
// Published variables will update view states when changed.
@Published var weightInKilograms: Bool
{ get {
// Get UserDefaults here
} set {
// Set UserDefaults here
}}
@Published var weight: Weight
}
If you pass an instance of MyGlobalClass
as an EnvironmentObject
to your main view, then to your secondary view, any changes made to properties in the global instance will update the views' state via the @Published
wrapper:
let global = MyGlobalClass()
/* ... */
// In your app's lifecycle, or where AccountView is instantiated
AccountView().environmentObject(global)
struct AccountView: View {
@EnvironmentObject var global: MyGlobalClass
var body: some View {
// ...
Text(global.weight.weightFormatted)
// ...
SecondaryView().environmentObject(global)
}
}