Uday Agarwal
Uday Agarwal

Reputation: 7

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

Upvotes: 1

Views: 181

Answers (1)

Sweeper
Sweeper

Reputation: 273540

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.

Upvotes: 1

Related Questions