When you use ForEach(0..<cards.count)
, you are using this initializer on ForEach
:
init(_ data: Range<Int>, content: @escaping (Int) -> Content)
And the docs for this initializer say: "The instance only reads the initial value of the provided data and doesn’t need to identify views across updates." In other words, cards.count
is read one time and then never again, even if you add or remove items from cards
.
So say you have 7 items in the array. cards.count
== 7. But then you remove one. cards.count
now == 6. But the ForEach
reads cards.count
only one time and then never again, remember, so ForEach
still thinks cards.count
== 7 even though cards.count
really == 6. What do you think will happen the next time ForEach
loops and tries to access the 7th item in cards
? There aren't that many items in the array, which causes an out of index error and the crash you are seeing.
In contrast, when you use ForEach(0..<cards.count, id: \.self)
, you are using this initializer on ForEach
:
init(_ data: Data, id: KeyPath<Data.Element, ID>, content: @escaping (Data.Element) -> Content)
And the docs for this initializer say this ForEach
can be used "to create views dynamically". So ForEach
will update its internal count of items each time the number of items in cards
changes.
Say you have 7 items in the array. cards.count
== 7. Then you remove one, making cards.count
== 6. ForEach
updates itself and now knows that cards.count
== 6 and won't crash.
The moral of the story is:
-
If your data is constant and will never have items added or removed, you can use the initializer like ForEach(0..<cards.count)
.
-
If your data is dynamic and the number of items can change, you can use the initializer like ForEach(0..<cards.count, id: \.self)