I have an Object like this (simplified):
struct BoxModel: Codable, Identifiable, Hashable {
var id: Int
var number: Int
}
I have a two dimensional array of these:
@State private var matrix = generate()
where generate creates a 2D array of the objects: [[BoxModel]]. I then display them in a grid. The grid is pretty complex but here's a very simplified form:
LazyVGrid(columns: columns, spacing: 0 ) {
ForEach(1...9, id: \.self) { j in
Text("\(getModel(j).number)")
}
}
Where getValue gets the item from the array that corresponds to the grid location. Since the grid is complex, I need to do some arithmetic in a separate function to do this.
Below this is a button. When the user taps it, it changes the number property of one of the objects in the array. However, the number doesn't update. I thought it would since the array is a @State property.
Is there a way to make it update?
Here's an extremely simplified form of the ContentView:
struct ContentView: View {
@State private var matrix = generate()
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(1...9, id: \.self) { j in
Text("\(getModel(j).number)")
}
}
.padding()
Button {
var model = getModel(0)
model.number = 5
} label: {
Text("Click Me")
}
}
}
}
}
}
func getModel(_ i: Int) -> BoxModel {
// very simplified
return matrix[0][i]
}
CodePudding user response:
Change the code in your Button
action closure.
var model = getModel(0)
model.number = 5
to
if let index = matrix[0].firstIndex(of: getModel(1)) {
matrix[0][index].number = 5
}
I think you're applying the concept of class
to a struct
, check out the Difference between class and struct
CodePudding user response:
The reason you are not seeing the update is because; BoxModel
is a struct
and there is no real change to
matrix
, only the content of one of its item has changed.
There are a number of ways to answer your question: "Is there a way to make it update?".
One simple minded way is shown in the code below, where BoxModel
is a class
, and there
is @State private var refresh
that is changed to trigger a view refresh.
class BoxModel: Codable, Identifiable, Hashable {
var id: Int
var number: Int
init(id: Int, number: Int) {
self.id = id
self.number = number
}
static func == (lhs: BoxModel, rhs: BoxModel) -> Bool {
lhs.id == rhs.id
}
func hash(into hasher: inout Hasher) {
hasher.combine(id)
}
}
struct ContentView: View {
@State private var refresh = false // <-- here
@State private var matrix = generate()
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(1...9, id: \.self) { j in
Text("\(getModel(j).number)")
}
}.padding(refresh ? 0 : 0) // <-- here
.padding()
Button {
var model = getModel(0)
model.number = 5
refresh.toggle() // <-- here
} label: {
Text("Click Me")
}
}
}
}
}
}
}
Although this should work, I suggest you re-structure your code and use a ObservableObject
for your BoxModel
.
Something like this example code:
class BoxModel: ObservableObject {
@Published var matrix: [[Box]] = generate()
func getBox(_ j: Int) -> Box {
// ...
}
func updateBox(_ j: Int, with n: Int) {
// ...
}
}
struct Box: Codable, Identifiable, Hashable {
var id: Int
var number: Int
}
struct ContentView: View {
@StateObject var boxModel = BoxModel()
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(1...9, id: \.self) { j in
Text("\(boxModel.getBox(j).number)")
}
}
.padding()
Button {
boxModel.updateBox(0, with: 5)
} label: {
Text("Click Me")
}
}
}
}
}
}
}
Note, depending on how you implement getModel()/getBox()
you may need to adjust ForEach(1...9, id: \.self) {...}
to
ForEach(0...8, id: \.self) {...}
since array index start at zero.