ForEach(0..<tasks.count) { index in
Be aware, however, that using this version of ForEach
can result in crashes if you add to and will result in crashes if you delete from your tasks
array. Using ForEach
with a Range
is intended for constant data that doesn't change. SwiftUI will read it once and then not again, meaning if you change the underlying array, those changes won't get noticed and will lead to crashes.
(And if you aren't planning to add to or delete from your tasks
array, then there's no need to make it an @State
variable.)
Instead, you can use something like this:
ForEach(Array(tasks.enumerated()), id: \.1.id) { idx, task in
Button {
tasks[idx].completed.toggle()
} label: {
HStack {
Image(systemName: task.completed ? "checkmark.circle" : "circle")
Text(task.name)
// NOTE: you can also use tasks[idx] here instead of task
}
}
}
With Swift 5.5, it becomes even easier:
ForEach($tasks) { $task in
Button {
task.completed.toggle()
} label: {
HStack {
Image(systemName: task.completed ? "checkmark.circle" : "circle")
Text(task.name)
}
}
}
And the great thing about this is, since it's a language feature and not a SwiftUI feature, as long as you are compiling against Swift 5.5 (which for the moment requires Xcode 13 beta), it will work in iOS 13 and 14 as well.
(Another note: When looping over an array of Identifiable
things, you don't need to include the id: \.id
parameter to ForEach
. It is implicit in the things being Identifiable
. So, ForEach(tasks)
instead of ForEach(tasks, id: \.id)
. Just FYI.)