Issue #796

One seemingly obvious way to use ForEach on array with indices is using enumerated

struct Book: Hashable, Identifiable {
    let id: UUID
    let name: String
}

struct BooksView: View {
    let books: [Book]

    var body: some View {
        List {
            ForEach(Array(books.enumerated()), id: \.element) { index, book in
                Text(book.name)
                    .background(index % 2 == 0 ? Color.green : Color.orange)
            }
        }
    }
}

Reading the documentation for enumerated closely

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)
    }
}