[SOLVED] Set all variables in view

Issue

Question: How can I easily change all variables in separate views inside a ForEach statement? (View remarks in code.)

ContentView()

@State var isShowing = false

var body: some View {
    Toggle("IsShowing", isOn: $isShowing)
          .onChange(of: isShowing) {
              if isShowing {
                 //Set all var `Toggled` in ToggleView() to `true`
              } else {
                 //Set all var `Toggled` in ToggleView() to `false`
                }    
          }
    Spacer()
    if IsShowing {
        ForEach(StaffList, id: \.self) { item in
              ToggledView()
        }
    }
}

ToggledView()

@State var Toggled = false

var body: some View {
     if Toggled {
        Text("True")
    } else {
        Text("False")
    }
}

If you have any questions, please ask.

Solution

The code you presented in your question has some issues and it is not immediately reproducible; let me exemplify:

  • You have two different variables, IsShowing and isShowing: is that a typo, or they are supposed to be different? IsShowing completely hides the list. I will assume that they have different purposes.
  • It is better to follow the convention in Swift: all variable start lower cased, so avoid IsShowing or StaffList as variable names.
  • The list in the code above is not visible anyhow because, when you have more than one view in the body, you need to embed them in a container, like a VStack.
  • onChange(of:perform:) takes one parameter in the closure, the code above does not compile – watch out, because SwiftUI throws errors that are misleading in this case.
  • You want to change all toggles in ToggledView: the variable toggled could be used for that purpose, but I will assume that it has a local scope, so I added a Toggle in my example below.

Coming to the solution: you need to bind the variable in the parent view with another variable in the child view, then listen to changes in the child variable.

The parent view has an @State variable, it needs to be passed to a @Binding variable in the child view. You listen to the changes with .onChange(of:) { }.

In the code below, each item in the list has their own specific toggle, but when you change the one on the top of the view, all of them will change accordingly.

Parent view:

struct ContentView: View {
    let isShowingList = true   // This replaces the "IsShowing" variable in the question
    
    @State var isShowing = false
    
    var body: some View {
        
        // Embed content in a container, like VStack
        VStack {
            
            Toggle("Toggle all items", isOn: $isShowing)
                .padding()
            
            Spacer()
            
            if isShowingList {
                ForEach(staffList, id: \.self) { item in
                    
                    // Pass the variable from this view to the child view
                    ToggledView(globalToggle: $isShowing)
                }
            }
            
            Spacer()
        }
        .padding()
    }
}

Child view:

struct ToggledView: View {
    
    // Make state variables private
    @State private var toggled = false
    
    // This is where the magic happens: changing iShowing in the parent view
    // will change globalToggle in this view
    @Binding var globalToggle: Bool
    
    var body: some View {
        Toggle(toggled ? "True" : "False", isOn: $toggled)

        // .onChange(of:) closure receives one parameter: the variable changed
            .onChange(of: globalToggle) { value in
                toggled = value
            }
    }
}

Answered By – HunterLion

Answer Checked By – Willingham (BugsFixing Volunteer)

Leave a Reply

Your email address will not be published.