I'm using a ForEach to display the contents of an array, then manually showing a divider between each element by checking the element index. Here's my code:
struct ContentView: View {
let animals = ["Apple", "Bear", "Cat", "Dog", "Elephant"]
var body: some View {
VStack {
/// array of tuples containing each element's index and the element itself
let enumerated = Array(zip(animals.indices, animals))
ForEach(enumerated, id: \.1) { index, animal in
Text(animal)
/// add a divider if the element isn't the last
if index != enumerated.count - 1 {
Divider()
.background(.blue)
}
}
}
}
}
Result:

This works, but I'd like a way to automatically add dividers everywhere without writing the Array(zip(animals.indices, animals)) every time. Here's what I have so far:
struct ForEachDividerView<Data, Content>: View where Data: RandomAccessCollection, Data.Element: Hashable, Content: View {
var data: Data
var content: (Data.Element) -> Content
var body: some View {
let enumerated = Array(zip(data.indices, data))
ForEach(enumerated, id: \.1) { index, data in
/// generate the view
content(data)
/// add a divider if the element isn't the last
if let index = index as? Int, index != enumerated.count - 1 {
Divider()
.background(.blue)
}
}
}
}
/// usage
ForEachDividerView(data: animals) { animal in
Text(animal)
}
This works great, isolating all the boilerplate zip code and still getting the same result. However, this is only because animals is an array of Strings, which conform to Hashable — if the elements in my array didn't conform to Hashable, it wouldn't work:
struct Person {
var name: String
}
struct ContentView: View {
let people: [Person] = [
.init(name: "Anna"),
.init(name: "Bob"),
.init(name: "Chris")
]
var body: some View {
VStack {
/// Error! Generic struct 'ForEachDividerView' requires that 'Person' conform to 'Hashable'
ForEachDividerView(data: people) { person in
Text(person.name)
}
}
}
}
That's why SwiftUI's ForEach comes with an additional initializer, init(_:id:content:), that takes in a custom key path for extracting the ID. I'd like to take advantage of this initializer in my ForEachDividerView, but I can't figure it out. Here's what I tried:
struct ForEachDividerView<Data, Content, ID>: View where Data: RandomAccessCollection, ID: Hashable, Content: View {
var data: Data
var id: KeyPath<Data.Element, ID>
var content: (Data.Element) -> Content
var body: some View {
let enumerated = Array(zip(data.indices, data))
/// Error! Invalid component of Swift key path
ForEach(enumerated, id: \.1.appending(path: id)) { index, data in
content(data)
if let index = index as? Int, index != enumerated.count - 1 {
Divider()
.background(.blue)
}
}
}
}
/// at least this part works...
ForEachDividerView(data: people, id: \.name) { person in
Text(person.name)
}
I tried using appending(path:) to combine the first key path (which extracts the element from enumerated) with the second key path (which gets the Hashable property from the element), but I got Invalid component of Swift key path.
How can I automatically add a divider in between the elements of a ForEach, even when the element doesn't conform to Hashable?
