1

This is so easy to do in UIKit, struggling with SwiftUI. I have in my ContentView a series of custom buttons. Each has their own @State variable which governs whether are toggled on or off - and update accordingly.

struct AudioToggleButton: View, Hashable {

    // Some Details omitted here for brevity
    @State private var toggled = false

    var body: some View {
        Button(action: {
            toggled.toggle()
        }) {
            Image(systemName: iconString)
                .frame(width: 70, height: 70)
                .foregroundColor(self.toggled == true ? .white : Color(UIColor.lightGray))
                .background(self.toggled == true ? .black : .white)
                .font(.title)
                .clipShape(Circle())
                .overlay(Circle()
                    .stroke(self.toggled == true ? Color(UIColor.darkGray) : Color(UIColor.lightGray), lineWidth: 6))
            .padding(4)
    }
}

I have in my ContentView where they are placed (a bunch). I have a regular vanilla button in my ContentView where I want to turn off all the buttons (the toggle) at once. I can change each by having them in an array & looping over them - and having a func in the custom button's struct that sets the @state variable - but the UI never updates to change because of the var change.

How can I turn off (toggle) any buttons that are toggled on from outside the buttons?

1
  • Btw. You don’t need to test a Bool in Swift, use like self.toggled ? .black : .white Commented Nov 4, 2023 at 13:39

2 Answers 2

1

Using an array will cause issues with updates, as you have discovered.

I expect you might get some answers on how to solve it with that approach. But an alternative way to do it is to have a simple struct that holds all the flags. This way, you only need one state variable. To reset all the flags at once, just create a new instance of the struct.

Like this:

struct MyFlags {
    var flag1 = false
    var flag2 = false
    var flag3 = false
}

struct ContentView: View {
    @State private var flags = MyFlags()
    var body: some View {
        Form {
            Section {
                Toggle("Flag 1", isOn: $flags.flag1)
                Toggle("Flag 2", isOn: $flags.flag2)
                Toggle("Flag 3", isOn: $flags.flag3)
            }
            Button("Reset") {
                flags = MyFlags()
            }
        }
    }
}

In your case, the state variable with all the flags would be held by the parent class and you would pass each individual flag to an AudioToggleButton as a Binding. Something like:

Something like:

struct AudioToggleButton: View, Hashable {

    @Binding var toggled: Bool

    // everything else as before
}

struct ContentView: View {
    @State private var flags = MyFlags()
    var body: some View {
        Form {
            Section {
                AudioToggleButton(toggled: $flags.flag1)
                // etc.
            }
            Button("Reset") {
                flags = MyFlags()
            }
        }
    }
}
Sign up to request clarification or add additional context in comments.

1 Comment

Thank you - I used this approach and I like it. Did not think about creating a new instance of the struct to "reset" things.
0

To control this state from outside the button, you need to make this state accessible from the parent view. This can be achieved by using @Binding and @State together.

struct AudioToggleButton: View, Hashable {
   // Some Details omitted here for brevity
   @Binding var toggled: Bool

   var body: some View {
       Button(action: {
           toggled.toggle()
       }) {
           Image(systemName: iconString)
               .frame(width: 70, height: 70)
               .foregroundColor(self.toggled == true ? .white : Color(UIColor.lightGray))
               .background(self.toggled == true ? .black : .white)
               .font(.title)
               .clipShape(Circle())
               .overlay(Circle()
                  .stroke(self.toggled == true ? Color(UIColor.darkGray) : Color(UIColor.lightGray), lineWidth: 6))
           .padding(4)
       }
   }
}

Now, you can control the toggled state from the parent view. Here's an example of how to use the AudioToggleButton in a parent view and control its state, so you can do like this:

struct ParentView: View {
   @State private var buttonToggled = false

   var body: some View {
       VStack {
           AudioToggleButton(toggled: $buttonToggled)
           Button("Toggle Button") {
               buttonToggled.toggle()
           }
       }
   }
}

2 Comments

I am trying to change the states from a class, not a View struct using this approach and I get "Accessing State's value outside of being installed on a View. This will result in a constant Binding of the initial value and will not update."
It's because you're trying to access a @State property from outside the view. I should be used only within a view and it's not designed to be accessed from other classes or structs.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.