@AppStorage
would not work as you’d anticipate when utilized in an @Observable
class, however there are methods round it. See the query Is there any manner to make use of @AppStorage with @Observable? for some options.
Mainly, used such as you did it is not going to drive state adjustments to your view in a manner that may enable it to show the up to date worth chosen within the Picker.
However here is one other method to go about it that may also possibly streamline your code. As a substitute of utilizing @AppStorage
in your class, take away it and use a didSet
closure to replace UserDefaults every time the worth adjustments. Use the initializer to set the preliminary worth:
@Observable
class LanguageSettings {
static let shared = LanguageSettings()
var currentLanguage: String {
didSet {
UserDefaults.commonplace.set(currentLanguage, forKey: "currentLanguage")
}
}
personal init() {
currentLanguage = UserDefaults.commonplace.string(forKey: "currentLanguage") ?? ""
}
}
Then, you’ll be able to replace the worth usually:
@Bindable var settings = LanguageSettings.shared
//...
Picker("Choose language", choice: $settings.currentLanguage) {
//...
}
And likewise show it:
Textual content(settings.currentLanguage)
Various:
You can additionally simply use @AppStorage
because it’s supposed (as a state that drives UI adjustments), by together with it in each your view and the editor/picker, and utilizing the observable class property for merely studying it (though it’s going to nonetheless not be observable with out @AppStorage current within the view).
Each strategies are proven within the instance code under. You possibly can experiment by eradicating the AppStorage state from the settings view and simply referencing the settings.username
property within the labeled content material’s worth, to see that the up to date worth within the editor will now solely present after a view refresh (as a result of lacking AppStorage state, with out which there isn’t any state being tracked or mutated in reminiscence).
Here is the complete working code:
import SwiftUI
@Observable
class LanguageSettings {
static let shared = LanguageSettings()
//This property units the worth in person defaults when the worth adjustments (and likewise retrieves it for studying)
var currentLanguage: String {
didSet {
UserDefaults.commonplace.set(currentLanguage, forKey: "currentLanguage")
}
}
//This property simply reads the worth from person defaults
var username: String {
UserDefaults.commonplace.string(forKey: "username") ?? ""
}
personal init() {
currentLanguage = UserDefaults.commonplace.string(forKey: "currentLanguage") ?? ""
}
}
struct LanguageSettingsView: View {
//Observables
let settings = LanguageSettings.shared
//State values
@State personal var showSheet = true
//Person defaults
@AppStorage("username") personal var username: String = "user123"
//Physique
var physique: some View {
Listing {
LabeledContent {
Button {
showSheet.toggle()
} label: {
Textual content(username)
}
} label : {
Textual content("Username")
Textual content("From settings: (settings.username)")
}
LabeledContent("Chosen language") {
Button {
showSheet.toggle()
} label: {
Textual content(settings.currentLanguage)
}
}
}
.contentMargins(.vertical, 20)
.sheet(isPresented: $showSheet) {
LanguageSettingsEditorView()
.presentationDetents([.height(150)])
.presentationBackgroundInteraction(.enabled)
.presentationDragIndicator(.seen)
}
}
}
struct LanguageSettingsEditorView: View {
//Observables
@Bindable var settings = LanguageSettings.shared
//Setting values
@Setting(.dismiss) var dismiss
//Person defaults
@AppStorage("username") personal var username: String = "user123"
//Physique
var physique: some View {
Kind {
Part {
//Username
LabeledContent("Set username") {
TextField("Username", textual content: $username)
.fixedSize()
.multilineTextAlignment(.trailing)
}
//Language picker
Picker("Choose language", choice: $settings.currentLanguage) {
Textual content("English").tag("English")
Textual content("French").tag("French")
Textual content("Spanish").tag("Spanish")
}
} header: {
HStack {
Textual content("Change language")
Spacer()
Button {
dismiss()
} label: {
Picture(systemName: "xmark.circle.fill")
.imageScale(.small)
}
}
}
}
}
}
//Preview
#Preview {
NavigationStack {
LanguageSettingsView()
.navigationTitle("Settings")
}
}
Be aware: In your case, the observable is a property of a singleton and requires passing to a view, however the code above makes use of a simplified strategy, the place the editor accesses the singleton straight. I feel you may get the thought.