swiftui

Updating ButtonStyle dynamically


I am trying to create a custom ButtonStyle which responds to changes in state. I can’t get it to update when the state changes.

Here is a simplified version:

struct TestButton: ButtonStyle {
    @State var selected: Bool = true
    func makeBody(configuration: ButtonStyleConfiguration) -> some View {
        configuration.label
            .frame(width: 48)
            .padding(EdgeInsets(top: 3, leading: 4, bottom: 3, trailing: 4))
            .background(selected ? .green : .red)
            .cornerRadius(14)
    }
}

struct TestView: View {
    @AppStorage("test") var test = 1
    var body: some View {
        HStack {
            Button(test == 1 ? "Yes" : "No") { test = 1 }
                .buttonStyle(TestButton(selected: test == 1))
            Button(test == 2 ? "Yes" : "No") { test = 2 }
                .buttonStyle(TestButton(selected: test == 2))
            Button(test == 3 ? "Yes" : "No") { test = 3 }
                .buttonStyle(TestButton(selected: test == 3))
        }
//      .id(UUID())     //  trying to avoid this
        .padding()
    }
}

The variable test is set by the buttons, and I want it to change the appearance of the button. The test test == … works correctly as the text switches between Yes and No. However, when I use it with the selected parameter, it doesn’t change anything. It sets the value when the view is first loaded, but doesn’t update.

The variable in the View uses @AppStorage, but I’ve tried with @State and get the same results.

I know that I can fake it by adding .id(UUID()), but I’m not sure whether it’s the best approach.

Ho can I update the ButtonStyle?


Solution

  • Just define selected be a normal property. Here is an example, I just change @AppStorage to a @State to easier for testing:

    struct TestButton: ButtonStyle {
        let selected: Bool
        func makeBody(configuration: ButtonStyleConfiguration) -> some View {
            configuration.label
                .frame(width: 48)
                .padding(EdgeInsets(top: 3, leading: 4, bottom: 3, trailing: 4))
                .background(selected ? .green : .red)
                .cornerRadius(14)
        }
    }
        
    struct TestView: View {
        @State var test = 1
        var body: some View {
            HStack {
                Button(test == 1 ? "Yes" : "No") { test = 1 }
                    .buttonStyle(TestButton(selected: test == 1))
                Button(test == 2 ? "Yes" : "No") { test = 2 }
                    .buttonStyle(TestButton(selected: test == 2))
                Button(test == 3 ? "Yes" : "No") { test = 3 }
                    .buttonStyle(TestButton(selected: test == 3))
            }
            .padding()
        }
    }
    

    When using @State var selected in ButtonStyle, It just recevie the value when a identity created, adding .id(UUID()) work because it cause current ButtonStyle to be destroy and a new Identity to be created therefore ButtonStyle will received selected change. I recommend you to watch "Demystify SwiftUI" to have more understand.

    enter image description here