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 Emerge

SPONSORED Why are Swift reference types bad for app startup time, and what’s the performance cost of protocol conformances? That’s just a couple of the topics you can learn about on the Emerge blog — written by the app performance experts behind Emerge’s advanced app optimization and monitoring tools, based on their experience of working at companies like Apple, Airbnb, Snap, and Spotify.

Find out more

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.