0
votes

try to save user setting, but UserDefaults is not working, Xcode 12.3, swiftui 2.0, when I am reload my app, my setting not updating for new value)

 class PrayerTimeViewModel: ObservableObject {
@Published var lm = LocationManager()
@Published var method: CalculationMethod = .dubai {
    didSet {
        UserDefaults.standard.set(method.params, forKey: "method")
        self.getPrayerTime()
    }
}
  
func getPrayerTime() {
    let cal = Calendar(identifier: Calendar.Identifier.gregorian)
    let date = cal.dateComponents([.year, .month, .day], from: Date())
    let coordinates = Coordinates(latitude: lm.location?.latitude ?? 0.0, longitude: lm.location?.longitude ?? 0.0)
    var par = method.params
    par.madhab = mashab
    self.times = PrayerTimes(coordinates: coordinates, date: date, calculationParameters: par)
}

and view.. update with AppStorage

 struct MethodView: View {

@ObservedObject var model: PrayerTimeViewModel
@Environment(\.presentationMode) var presentationMode
@AppStorage("method", store: UserDefaults(suiteName: "method")) var method: CalculationMethod = .dubai
var body: some View {
    List(CalculationMethod.allCases, id: \.self) { item in
        Button(action: {
            self.model.objectWillChange.send()
            self.presentationMode.wrappedValue.dismiss()
            self.model.method = item
            method = item
        }) {
            HStack {
                Text("\(item.rawValue)")
                if model.method == item {
                    Image(systemName: "checkmark")
                        .foregroundColor(.black)
                }
            }
        }
    }
}

}

2
Where do you load the value from UserDefaults? You should also show the code for CalculationMethod - Andrew
I am update my view with AppStorage, checkOut, my skill swiftui Junior) maybe I don't unde - Kamilpro
You still haven't shown CalculationMethod, what is that? what is method.params? It also looks like you are storing and retrieving from two different UserDefaults suites (standard and method). - Andrew
CalculationMethod it is enum with cases, so params in is var inside this enum, he is switches case and return some paraments, So User only choice case in SettingView, But I can't save) - Kamilpro
You seem to be saving the params in userdefaults but then using the enum with your AppStorage property wrapper. Can you should the code for CalculationMethod? - Andrew

2 Answers

1
votes

You have two issues.

First, as I mentioned in my comment above that you are using two different suites for UserDefaults. This means that you are storing and retrieving from two different locations. Either use UserDefaults.standard or use the one with your chosen suite UserDefaults(suitName: "method") - you don't have to use a suite unless you plan on sharing your defaults with other extensions then it would be prudent to do so.

Secondly you are storing the wrong item in UserDefaults. You are storing a computed property params rather than the actual enum value. When you try to retrieve the value it fails as it is not getting what it expects and uses the default value that you have set.

Here is a simple example that shows what you could do. There is a simple enum that has a raw value (String) and conforms to Codable, it also has a computed property. This matches your enum.

I have added an initialiser to my ObservableObject. This serves the purpose to populate my published Place from UserDefaults when the Race object is constructed.

Then in my ContentView I update the place depending on a button press. This updates the UI and it updates the value in UserDefaults.

This should be enough for you to understand how it works.

enum Place: String, Codable {
    case first
    case second
    case third
    case notPlaced

    var someComputedProperty: String {
        "Value stored: \(self.rawValue)"
    }
}

class Race: ObservableObject {
    @Published var place: Place = .notPlaced {
        didSet {
            // Store the rawValue of the enum into UserDefaults
            // We can store the actual enum but that requires more code
            UserDefaults.standard.setValue(place.rawValue, forKey: "method")

            // Using a custom suite
            // UserDefaults(suiteName: "method").setValue(place.rawValue, forKey: "method")
        }
    }

    init() {
        // Load the value from UserDefaults if it exists
        if let rawValue = UserDefaults.standard.string(forKey: "method") {
            // We need to nil-coalesce here as this is a failable initializer
            self.place = Place(rawValue: rawValue) ?? .notPlaced
        }

        // Using a custom suite
        // if let rawValue = UserDefaults(suiteName: "method")?.string(forKey: "method") {
        //    self.place = Place(rawValue: rawValue) ?? .notPlaced
        // }
    }
}

struct ContentView: View {
    @StateObject var race: Race = Race()

    var body: some View {
        VStack(spacing: 20) {
            Text(race.place.someComputedProperty)
                .padding(.bottom, 20)
            Button("Set First") {
                race.place = .first
            }
            Button("Set Second") {
                race.place = .second
            }
            Button("Set Third") {
                race.place = .third
            }
        }
    }
}

Addendum:

Because the enum conforms to Codable it would be possible to use AppStorage to read and write the property. However, that won't update the value in your ObservableObject so they could easily get out of sync. It is best to have one place where you control a value. In this case your ObservableObject should be the source of truth, and all updates (reading and writing to UserDefaults) should take place through there.

0
votes

You write in one UserDefaults domain but read from the different. Assuming your intention is to use suite only UserDefaults, you should change one in model, like

@Published var method: CalculationMethod = .dubai {
    didSet {
        UserDefaults(suiteName: "method").set(method.params, forKey: "method")
        self.getPrayerTime()
    }
}

or if you want to use standard then just use AppStorage with default constructor, like

// use UserDefaults.standard by default
@AppStorage("method") var method: CalculationMethod = .dubai