Search code examples
swiftswiftuiappstorage

@AppStorage in Structs


I am really stuck on this. I want to make several button, each having independet state of ifButtonDisabled. The button disables when user clicks on it. Since, I want my app to remember the state of the button when the app reloads or refresh, I am using AppStorage. The problem is since each button has independent state, AppStorage variable should also be different. But since self is not avaialble, I am not able to have a dnamic name for my AppStorage variable.

Also, Is there any better alternative to achieve the same functionality???

struct ButtonData {
    var id: UUID {
        return UUID()
    }
    let level: String
    let bgColor: String
    let image: String
    @AppStorage(level) var ifDisabled: Bool = false
}

Solution

  • I'm not sure why you would want to have an id that changes every time you access it, but if you just want to initialise isDisabled, you can write your own init, and assign an AppStorage to _isDisabled there.

    struct ButtonData {
        let level: String
        let bgColor: String
        let image: String
    
        // note that nothing is passed to @AppStorage here
        // and this property is not initialised to anything - we'll do that in init
        @AppStorage var isDisabled: Bool
        
        init(level: String, bgColor: String, image: String) {
            self.level = level
            self.bgColor = bgColor
            self.image = image
            // this is where isDisabled is initialised to false,
            // and the AppStorage with the level as key is created.
            self._isDisabled = AppStorage(wrappedValue: false, level)
        }
    }
    

    That said, your view won't update if you want to use it like this:

    struct ContentView: View {
        @State var buttonData = ButtonData(level: "foo", bgColor: "bar", image: "baz")
        var body: some View {
            Text("Foo")
                .onTapGesture {
    //                buttonData.isDisabled.toggle()
                }
            Button("Button") { ... }.disabled(buttonData.isDisabled)
        }
    }
    

    Because AppStorage has a nonmutating setter, so setting it won't cause buttonData in the view to mutate, and so SwiftUI doesn't know that your view needs updating.

    So if you want AppStorage to update views, you need to put it directly in a View. For example, you could make your own button view that takes in a ButtonData, and sets its disabled status accordingly:

    // ButtonData can simply be
    struct ButtonData {
        let level: String
        let bgColor: String
        let image: String
    }
    
    struct MyButton: View {
        let data: ButtonData
        @AppStorage var isDisabled: Bool
        init(data: ButtonData) {
            self.data = data
            self._isDisabled = AppStorage(wrappedValue: false, data.level)
        }
        
        var body: some View {
            // perhaps also provide init parameters for the action...
            Button {
                ...
            } label: {
                Image(data.image)
            }
            .disabled(isDisabled)
        }
    }
    

    Now whenever the superview updates the AppStorage, MyButton will update its disabled status too.