WWDC22 SALE: Save 50% on all my Swift books and bundles! >>

[Day 88] Question

Forums > 100 Days of SwiftUI

in this topic a sample code is:

ForEach(0..<cards.count, id: \.self) { index in
    CardView(card: cards[index]) {
       withAnimation {
           removeCard(at: index)
       }
    }
    .stacked(at: index, in: cards.count)
}

This run well when "removeCard" function execute

But below my code crash when "removeCard" function execute

ForEach(0..<cards.count) { index in
    CardView(card: cards[index]) {
       withAnimation {
           removeCard(at: index)
       }
    }
    .stacked(at: index, in: cards.count)
}

I didnt put id:\.self on the ForEach and it crash and show me message:

ForEach<Range<Int>, Int, ModifiedContent<ModifiedContent<CardView, _OffsetEffect>, _AllowsHitTestingModifier>> count (9) != its initial count (10). `ForEach(_:content:)` should only be used for *constant* data. Instead conform data to `Identifiable` or use `ForEach(_:id:content:)` and provide an explicit `id`!
Swift/ContiguousArrayBuffer.swift:580: Fatal error: Index out of range
2022-01-26 22:00:37.507500+0700 Flashzilla[11656:374861] Swift/ContiguousArrayBuffer.swift:580: Fatal error: Index out of range

Could someone explain for me what different between ForEach using id:.self and ForEac without id:.self ?

1      

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)

2      

thank you so much ! I got it now ^_^

1      

Hacking with Swift is sponsored by Fernando Olivares

SPONSORED Fernando's book will guide you in fixing bugs in three real, open-source, downloadable apps from the App Store. Learn applied programming fundamentals by refactoring real code from published apps. Hacking with Swift readers get a $10 discount!

Read the book

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.