ForEach
A structure that computes views on demand from an underlying collection of identified data
struct ForEach<Data, ID, Content> where Data :
RandomAccessCollection, ID :Hashable
- Data must conform to
RandomAccessCollection. - ID must conform to
Hashable.
Why ForEach uses id
init( _ data: Data, @ViewBuilder content: @escaping (Data.Element) -> Content )
In the default initializer, data must conform to the Identifiable protocol. (to identify each view)
But data types that don't conform to Identifiable, like [Int], can't be passed as data.
So if you try, you get a compile error. (Cannot convert value of type '[Int]' to expected argument type 'Range’)
To solve this, you use the initializer that has an id.
init( _ data: Data, id: KeyPath<Data.Element, ID>, @ViewBuilder content: @escaping (Data.Element) -> Content )
In this initializer, data doesn't have to conform to Identifiable.
That's because id takes over that role.
When using a ForEach view, people often pass \.self as the ID.
struct ContentView: View { let colors: [Color] = [.red, .green, .blue] var body: some View { VStack { ForEach(colors, id: \.self) { color in Text(color.description.capitalized) .padding() .background(color) } } } }
When the views created by ForEach are created or removed, it tells the views apart by their ID value so it can update only that part.
public init(_ data: Data, id: KeyPath<Data.Element, ID>, @ViewBuilder content: @escaping (Data.Element) -> Content)
The id argument passed to the ForEach view is passed as a keyPath, and through the referenced keyPath,
the value taken from data is decided as the unique identifier so that each view can be told apart.
So in the example above, since we passed \.self, which omits \Color.self, each view's unique identifier becomes .red, .green, .blue in order.
Then what should we do if elements in data are the same?
For example, when the colors array looks like this.
let colors: [Color] = [.red, .red, .blue]
One Stack Overflow answer recommends wrapping it in a struct so it conforms to the Identifiable protocol.
How to allow ForEach layout item to show duplicate item from array in SwiftUI?
Create a struct like the one below and generate a unique identifier with the UUID() function.
struct MyColor: Identifiable { var id = UUID() var color: Color }
Pass .id as the keyPath like below so each view becomes unique.
struct ContentView: View { let colors: [MyColor] = [.init(color: .red), .init(color: .red), .init(color: .blue)] var body: some View { VStack { ForEach(colors, id: \.id) { myColor in Text(myColor.color.description.capitalized) .padding() .background(myColor.color) } } } }