Home > Blockchain >  Update State when element of ForEach is updated without using Index
Update State when element of ForEach is updated without using Index

Time:03-02

I have the following simple ContentView:

struct ContentView: View {

@State private var matrix = [BoxModel(id: 0), BoxModel(id: 1), BoxModel(id: 2)]
@State private var chosen = BoxModel(id: -1)

var body: some View {
    NavigationView {
        GeometryReader { geometry in
            ScrollView {
                VStack {

                    let columns = [GridItem(.fixed(50), spacing: 0),
                                     GridItem(.fixed(50), spacing: 0),
                                     GridItem(.fixed(50), spacing: 0)]
                    

                    LazyVGrid(columns: columns, spacing: 0 ) {
                        ForEach(matrix) { box in
                            Button {
                                chosen = box
                            } label: {
                                Text("\(box.number)")
                            }
                        }
                    }
                    .padding()
                    Button {
                        chosen.number = 5
                    } label: {
                        Text("Click Me")
                    }
                }
            }
        }
    }
}
}

where the BoxModel is an object like this:

struct BoxModel: Codable, Identifiable, Hashable {
    var id: Int
    var number: Int
}

When the button is pressed I want the "box" to be updated with the new number. But I want to do this without using the index. I know that if I get the index of the box, I can do something like matrix[0].number = 5 and it'll work.

Is this possible?

CodePudding user response:

Your code shows boxes that are contained in the matrix array, while you have also created chosen which is a completely different instance.

You change chosen to store 5 in the "number" property, and I bet it's changing, but you are still only showing in the UI the instances of the boxes that are inside the matrix array, whatever happens to chosen will not be seen on the screen.

You need to completely drop the chosen variable, and have a button that changes each instance of "box" that is in the matrix. You might want to move your code inside the ForEach to another view too; that view would have a @State var of type BoxModel of its own, so you can see the changes as they happen.

CodePudding user response:

Like the answer I gave you in your previous question, you could use a ObservableObject like in this example code:

class BoxModel: ObservableObject {
    @Published var matrix: [Box] = [Box(id: 0, number: 0),Box(id: 1, number: 1), Box(id: 2, number: 2)]
    
    func updateBox(_ box: Box) {
        if let ndx = matrix.firstIndex(where: { $0.id == box.id }) {
            matrix[ndx] = box
        }
    }
    
}

struct Box: Codable, Identifiable, Hashable {
    var id: Int
    var number: Int
}

struct ContentView: View {
    @StateObject var boxModel = BoxModel()
    @State private var chosen = Box(id: -1, number: 0)
    
    var body: some View {
        NavigationView {
            GeometryReader { geometry in
                ScrollView {
                    VStack {
                        let columns = [GridItem(.fixed(50), spacing: 0),
                                       GridItem(.fixed(50), spacing: 0),
                                       GridItem(.fixed(50), spacing: 0)]
                        
                        LazyVGrid(columns: columns, spacing: 0 ) {
                            ForEach(boxModel.matrix) { box in
                                Button {
                                    chosen = box
                                } label: {
                                    Text("\(box.number)")
                                }
                            }
                        }
                        .padding()
                        Button {
                            chosen.number = 5
                            boxModel.updateBox(chosen)
                        } label: {
                            Text("Click Me")
                        }
                        Text("chosen number: \(chosen.number)").padding(20) // <-- for testing
                    }
                }
            }
        }
    }

}

If you definitely do not want to use ObservableObject such as in the code, let me know and I'll delete my answer.

  • Related