When you enumerate a collection, the integer part of each pair is a counter for the enumeration, but is not necessarily the index of the paired value. These counters can be used as indices only in instances of zero-based, integer-indexed collections, such as Array and ContiguousArray. For other collections the counters may be out of range or of the wrong type to use as an index. To iterate over the elements of a collection with its indices, use the zip(::) function.
We see that it works only for 0 based indexed collections like Array, a safer solution proposed in the docs is to use zip. Since our model conforms to Identifiable, we use id: \.1 to tell ForEach to use our model as identifiers. Here we can safely use index in the closure as it points to the actual index in the books collection
struct BooksView: View {
let books: [Book]
var body: some View {
List {
ForEach(Array(zip(books.indices, books)), id: \.1) { index, book in
Text(book.name)
.background(index % 2 == 0 ? Color.green : Color.orange)
}
}
}
}
I often encapsulate this logic into a reusable View
struct ForEachWithIndex<
Data: RandomAccessCollection,
Content: View
>: View where Data.Element: Identifiable, Data.Element: Hashable {
let data: Data
@ViewBuilder let content: (Data.Index, Data.Element) -> Content
var body: some View {
ForEach(Array(zip(data.indices, data)), id: \.1) { index, element in
content(index, element)
}
}
}
so we can use easily
ForEachWithIndex(data: viewModel.books) { index, book in
VStack {
BookRow(book: book)
if index < viewModel.books.count - 1 {
Divider()
}
}
}
We can also declare it as a free function to return ForEach directly
func ForEachWithIndex<
Data: RandomAccessCollection,
Content: View>(
_ data: Data,
@ViewBuilder content: @escaping (Data.Index, Data.Element) -> Content
) -> some View where Data.Element: Identifiable, Data.Element: Hashable {
ForEach(Array(zip(data.indices, data)), id: \.1) { index, element in
content(index, element)
}
}
One seemingly obvious way to use ForEach on array with indices is using
enumerated
Reading the documentation for enumerated closely
We see that it works only for 0 based indexed collections like
Array
, a safer solution proposed in the docs is to usezip
. Since our model conforms toIdentifiable
, we useid: \.1
to tellForEach
to use our model as identifiers. Here we can safely useindex
in the closure as it points to the actual index in thebooks
collectionI often encapsulate this logic into a reusable View
so we can use easily
We can also declare it as a free function to return
ForEach
directly