I'm trying to highlight a selected row in a foreach that's using classes. The following is working from an example on SO
struct Model: Identifiable {
var id = UUID()
var a: String
var b: String
var c: String
var isActive: Bool
}
struct ContentView: View {
@State private var modelArray = [
Model(a:"hi", b:"I like", c: "potatoes", isActive: true),
Model(a:"hi", b:"I like", c: "potatoes", isActive: false),
Model(a:"hi", b:"I like", c: "potatoes", isActive: false),
]
var body: some View {
ForEach(modelArray.indices, id: \.self){ idx in
Text("\(self.modelArray[idx].a) \(self.modelArray[idx].b) \(self.modelArray[idx].c)")
.foregroundColor(self.modelArray[idx].isActive ? .red : .green)
.onTapGesture {
self.modelArray[idx].isActive = true
for i in self.modelArray.indices {
if i != idx { self.modelArray[i].isActive = false }
}
}
}
}
}
But when I change the object to a class and initialize it, it will no longer highlight the text on tap. The first object that's initialized to true IS higlighted, and the print command will print the correct bool value, but the text doesn't change.
class Model: ObservableObject, Identifiable {
@Published var id = UUID()
@Published var a: String
@Published var b: String
@Published var c: String
@Published var isActive: Bool
init(a: String, b: String, c: String, isActive: Bool) {
self.a = a
self.b = b
self.c = c
self.isActive = isActive
}
}
@State private var modelArray = [
Model(a:"hi", b:"I like", c: "potatoes", isActive: true),
Model(a:"hi", b:"I like", c: "potatoes", isActive: false),
Model(a:"hi", b:"I like", c: "potatoes", isActive: false),
]
---
ForEach(modelArray.indices, id: \.self){ idx in
Text("\(self.modelArray[idx].a) \(self.modelArray[idx].b) \(self.modelArray[idx].c)")
.foregroundColor(self.modelArray[idx].isActive ? .red : .green)
.onTapGesture {
self.modelArray[idx].isActive.toggle()
print(self.modelArray[idx].isActive)
}
}
---
Any help would be greatly appreciated. I need the object to be a class, as I'm pulling data in from firebase in a view model.
Thanks
CodePudding user response:
try this approach, where you keep your Model
as struct, and
a class ViewModel: ObservableObject
that hold your array of [Model]
. This ViewModel
will observe
any changes to your modelArray
and update the UI.
Such as this example code
class ViewModel: ObservableObject {
@Published var modelArray: [Model] = [
Model(a:"hi", b:"I like", c: "potatoes", isActive: true),
Model(a:"hi", b:"I like", c: "potatoes", isActive: false),
Model(a:"hi", b:"I like", c: "potatoes", isActive: false),
]
}
in ContentView
@StateObject var viewModel = ViewModel()
and adjust the code accordingly. For example:
ForEach(viewModel.modelArray.indices, id: \.self){ idx in ...}
or simply
ForEach(viewModel.modelArray){ aModel in ...}
EDIT-1: for completeness, here is my test code:
class ViewModel: ObservableObject {
@Published var modelArray: [Model] = [
Model(a:"hi", b:"I like", c: "potatoes", isActive: true),
Model(a:"hi", b:"I like", c: "potatoes", isActive: false),
Model(a:"hi", b:"I like", c: "potatoes", isActive: false),
]
// -- here
func update(model: Model) {
for i in modelArray.indices {
if model.id == modelArray[i].id {
modelArray[i].isActive = true
} else {
modelArray[i].isActive = false
}
}
}
}
struct Model: Identifiable {
let id = UUID() // <-- here
var a: String
var b: String
var c: String
var isActive: Bool
}
struct ContentView: View {
@StateObject var viewModel = ViewModel() // <-- here
var body: some View {
ForEach(viewModel.modelArray){ model in // <-- here
Text("\(model.a) \(model.b) \(model.c)")
.foregroundColor(model.isActive ? .red : .green)
.onTapGesture {
viewModel.update(model: model) // <-- here
}
}
}
}