Updating item in nested observed array with swiftUI

I have an observed object called event view model, which handles the logic for all events. Each event has some todo items which need to be done and should be toggle-able. I have tried to solve this problem however, my code looks terrible and would be very bad to maintain later on.

Code Block swift
/// Models
struct TodoPG: Identifiable {
var id = UUID().uuidString
var title: String
var done = false
}
struct EventPG: Identifiable, Equatable {
var id = UUID().uuidString
var title: String
var todos: [TodoPG]
var totalTodos: Int {
return todos.filter{ !$0.done }.count
}
static func == (lhs: EventPG, rhs: EventPG) -> Bool {
return lhs.id == rhs.id
}
}


Code Block swift
/// View Models
class EventsViewModelPG: ObservableObject {
@Published var events: [EventPG]
init() {
let todos = [TodoPG(title: "todo item one", done: true), TodoPG(title: "todo item two"), TodoPG(title: "todo item three")]
self.events = [EventPG(title: "event one", todos: todos), EventPG(title: "event two", todos: todos)]
}
func update(event: EventPG) {
print("update")
self.events = events.map{ ($0.id == event.id) ? event : $0}
}
func addEvent(event: EventPG) {
self.events.append(event)
}
}


And lastly my horrible looking code
Code Block swift
/// Views
struct EventViewPG: View {
@ObservedObject var eventsVM: EventsViewModelPG
var body: some View {
VStack (alignment: .leading) {
List(eventsVM.events.indices) { eventIndex in
VStack {
Text(eventsVM.events[eventIndex].title)
.font(.title)
Text("items todo: \(eventsVM.events[eventIndex].totalTodos)")
.font(.title3)
ForEach(eventsVM.events[eventIndex].todos.indices) { todoIndex in
HStack {
Image(systemName: eventsVM.events[eventIndex].todos[todoIndex].done ? "checkmark.square" : "square")
.resizable()
.frame(width: 24, height: 24)
.foregroundColor(eventsVM.events[eventIndex].todos[todoIndex].done ? .blue : .gray)
.font(.system(size: 20, weight: .regular, design: .default))
Text(eventsVM.events[eventIndex].todos[todoIndex].title)
Spacer()
}.padding(5.0)
.onTapGesture(perform: {
eventsVM.events[eventIndex].todos[todoIndex].done.toggle()
})
}
}
}
}
}
}

Is there a simpler way to eliminate the keeping track of all the indices. Thank you in advanced. I've added a gist of the full code here: https://gist.github.com/yustarandomname/07fe9816523edd94382844787971e626
Okay, so on line 7:
Code Block swift
List(eventsVM.events.indices) { eventIndex in

rather than calling the indices, you could just call events directly?

Code Block swift
ForEach(eventsVM.events, id: \.id) { event in

Then do the same for the todos! You can now call the event we defined in the above line ^

Code Block swift
ForEach(event.todos, id: \.id) { todo in


I hope this helps! Any questions, let me know!

this peace of code

Code Block swift
ForEach(eventsVM.events[eventIndex].todos) { todo in
HStack {
         Image(systemName: todo.done ? "checkmark.square" : "square")
               .resizable()
               .frame(width: 24, height: 24)
               .foregroundColor(todo.done ? .blue : .gray)
               .font(.system(size: 20, weight: .regular, design: .default))
          Text(todo.title)
          Spacer()
      }.padding(5.0)
     .onTapGesture(perform: {
           todo.done.toggle()
      })
}


this will result in this error
""Cannot use mutating member on immutable value: 'todo' is a 'let' constant""

Is there a simpler way to eliminate the keeping track of all the indices.

As far as I know, there are no ways provided in the current SwiftUI framework.
You would be able to create some wrapper to make things look simpler, for example:
Code Block
struct ForEachMutable<Data, Content: View>: DynamicViewContent
where Data : RandomAccessCollection & MutableCollection,
Data.Index: Hashable
{
var data: Data {
dataBinding.wrappedValue
}
var dataBinding: Binding<Data>
var content: (Binding<Data.Element>)->Content
init(_ dataBinding: Binding<Data>, content: @escaping (Binding<Data.Element>)->Content) {
self.dataBinding = dataBinding
self.content = content
}
var body: some View {
ForEach(data.indices, id: \.self) {index in
content(dataBinding[index])
}
}
}

And use it as:
Code Block
var body: some View {
VStack (alignment: .leading) {
List {
ForEachMutable($eventsVM.events) { eventBinding in
VStack {
Text(eventBinding.wrappedValue.title)
.font(.title)
Text("items todo: \(eventBinding.wrappedValue.totalTodos)")
.font(.title3)
ForEachMutable(eventBinding.todos) { todoBinding in
HStack {
Image(systemName: todoBinding.wrappedValue.done ? "checkmark.square" : "square")
.resizable()
.frame(width: 24, height: 24)
.foregroundColor(todoBinding.wrappedValue.done ? .blue : .gray)
.font(.system(size: 20, weight: .regular, design: .default))
Text(todoBinding.wrappedValue.title)
Spacer()
}.padding(5.0)
.onTapGesture(perform: {
todoBinding.wrappedValue.done.toggle()
})
}
}
}
}
}
}


Updating item in nested observed array with swiftUI
 
 
Q