ios – Customized Binding property inside View doesn’t replace that View when modified

ios – Customized Binding property inside View doesn’t replace that View when modified


@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.

enter image description here

Leave a Reply

Your email address will not be published. Required fields are marked *