Home > Enterprise >  Swift foreach selected row with class object
Swift foreach selected row with class object

Time:08-08

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
                }
        }
    }
}
  • Related