0

I am trying to change the color programmatically of some buttons in SwiftUI. The buttons are stored in a LazyVGrid. Each button is built via another view (ButtonCell). I'm using a @State in the ButtonCell view to check the button state. If I click on the single button, his own state changes correctly, just modifying the @State var of the ButtonCell view. If I try to do the same from the ContentView nothing is happening.

This is my whole ContentView (and ButtonCell) view struct:

struct ContentView: View {
    
    private var gridItemLayout = [GridItem(.adaptive(minimum: 30))]
    
    var body: some View {
        let columns = [
            GridItem(.flexible()),
            GridItem(.flexible()),
            GridItem(.flexible()),
            GridItem(.flexible()),
            GridItem(.flexible())
        ]
        ScrollView {
            LazyVGrid(columns: columns, spacing: 0) {
                ForEach(0..<10) { number in
                    ButtonCell(value: number + 1)
                }
            }
        }
        Button(action: {
            ButtonCell(value: 0, isEnabled: true)
            ButtonCell(value: 1, isEnabled: true)
            ButtonCell(value: 1, isEnabled: true)
            
        }){
            Rectangle()
                .frame(width: 200, height: 50)
                .cornerRadius(10)
                .shadow(color: .black, radius: 3, x: 1, y: 1)
                .padding()
                .overlay(
                    Text("Change isEnabled state").foregroundColor(.white)
                )
        }
        
    }
    
    
    struct ButtonCell: View {
        var value: Int
        @State var isEnabled:Bool = false
        
        var body: some View {
            Button(action: {
                print (value)
                print (isEnabled)
                isEnabled = true
                
            }) {
                Rectangle()
                    .foregroundColor(isEnabled ? Color.red : Color.yellow)
                    .frame(width: 50, height: 50)
                    .cornerRadius(10)
                    .shadow(color: .black, radius: 3, x: 1, y: 1)
                    .padding()
                    .overlay(
                        Text("\(value)").foregroundColor(.white)
                    )
            }
        }
        
    }
}

How I may change the color of a button in the LazyVGrid by clicking the "Change isEnabled state" button?

double-beep
  • 5,031
  • 17
  • 33
  • 41
JoLight
  • 5
  • 4
  • Note, `ButtonCell` is a `View`. You should not put one or more `View` in a `Button` action such as in your `ContentView`, it is not the appropriate place for `Views`. Read the the basics of SwiftUI, for example here: https://developer.apple.com/tutorials/swiftui/ – dudette Jan 03 '23 at 04:46

1 Answers1

-1

You need a different approach here. Currently you try to change the State of ButtonCell from the outside. State variables should always be private and therefore should not be changed from outside. You should swap the state and parameters of ButtonCell into a ViewModel. The ViewModels then are stored in the parent View (ContentView) and then you can change the ViewModels and the child views automatically update. Here is a example for a ViewModel:

final class ButtonCellViewModel: ObservableObject {
    @Published var isEnabled: Bool = false
    let value: Int
    
    init(value: Int) {
        self.value = value
    }
}

Then store the ViewModels in the ContentView:

struct ContentView: View {
    let buttonViewModels = [ButtonCellViewModel(value: 0), ButtonCellViewModel(value: 1), ButtonCellViewModel(value: 2)]
    
    private var gridItemLayout = [GridItem(.adaptive(minimum: 30))]
    
    var body: some View {
        let columns = [
            GridItem(.flexible()),
            GridItem(.flexible()),
            GridItem(.flexible()),
            GridItem(.flexible()),
            GridItem(.flexible())
        ]
        ScrollView {
            LazyVGrid(columns: columns, spacing: 0) {
                ForEach(0..<3) { index in
                    ButtonCell(viewModel: buttonViewModels[index])
                }
            }
        }
        Button(action: {
            buttonViewModels[0].isEnabled.toggle()
        }){
            Rectangle()
                .frame(width: 200, height: 50)
                .cornerRadius(10)
                .shadow(color: .black, radius: 3, x: 1, y: 1)
                .padding()
                .overlay(
                    Text("Change isEnabled state").foregroundColor(.white)
                )
        }
    }
}

And implement the ObservedObject approach in ButtonCell.

struct ButtonCell: View {
    @ObservedObject var viewModel: ButtonCellViewModel
    
    var body: some View {
        Button(action: {
            print (viewModel.value)
            print (viewModel.isEnabled)
            viewModel.isEnabled = true
            
        }) {
            Rectangle()
                .foregroundColor(viewModel.isEnabled ? Color.red : Color.yellow)
                .frame(width: 50, height: 50)
                .cornerRadius(10)
                .shadow(color: .black, radius: 3, x: 1, y: 1)
                .padding()
                .overlay(
                    Text("\(viewModel.value)").foregroundColor(.white)
                )
        }
    }
}
davidev
  • 7,694
  • 5
  • 21
  • 56