Enumerating Tuples In SwiftUI
swift, swiftui, tuples, list – Feb 13 2021
ForEach with an Array of Tuples can cause some unexpected results, if your id that your using is not unique. Here is a story.
• • •
Overview
ForEach
is expecting that your data has some unique id
. In this example that is the Integer. If it’s not unique you will run into issues. This example is probably too simple but it will help illustrate a point.
This example it works fine because id:\.0
is an Int
and is unique
struct Example: View {
var tupleListExample: [(Int, String)] = [
(1, "A"),
(2, "B"),
(3, "C"),
]
var body: some View {
VStack {
// NOTE: \.0 is referencing (1, "A")
// and if it was \.1 it would be referencing "A" from the tuple
ForEach(tupleListExample, id:\.0) { item in
HStack {
Text("\(item.0)")
Spacer()
Text(item.1)
}
}
}
}
}
Example of Problems
You will run into problems when the id
is not unique.
I ran into this problem assuming I could use Date()
as a unique idenitifer
. In this example it sometimes work and soemtimes doesn’t because these dates get init
at the same time. Meaning they could all have the same Date()
or not.
From this example I came to the conclusion that a Date()
is not reliable identifier
because you could have items in the collection that could have the same Date()
.
struct Example: View {
var tupleListExample: [(Date, String)] = [
(Date(), "A"),
(Date(), "B"),
(Date(), "C"),
]
var body: some View {
VStack {
// This may or may not work as the Date() could all be the same
ForEach(tupleListExample, id:\.0) { item in
HStack {
Text("\(item.0)")
Spacer()
Text(item.1)
}
}
}
}
}
Conclusion
If you find yourself looping through an array of tuples, maybe consider converting them into some struct that conforms to the Identifiable
protocol and make the id property a UUID
. As this would guarantee that the id
is truly unique.
Here is an example of how to do so.
import SwiftUI
import Combine
// 01 Create a custom struct that conforms to Identifiable
struct Foo: Identifiable {
var id: UUID = UUID()
var date: Date
var letter: String
}
// 02 Create view model for this view
class ExampleViewModel: ObservableObject {
@Published var results: [Foo] = []
func updateResults(using tuples: [(Date, String)]) {
let newResults = tuples.map({Foo.init(date: $0.0, letter: $0.1)})
results = newResults
}
}
// 03 Your View
struct Example: View {
// 4 Create the model as a `StateObject`
@StateObject private var model: ExampleViewModel = ExampleViewModel()
var tupleListExample: [(Date, String)] = [
(Date(), "A"),
(Date(), "B"),
(Date(), "C"),
]
var body: some View {
VStack {
// 5 You can now reference the results from the model and use the `id`
// by default the results will be empty but in the `onAppear` you will
// see how this will return the results we want
ForEach(model.results, id:\.id) { item in
HStack {
Text("\(item.date)")
Spacer()
Text(item.letter)
}
}
}.onAppear(perform: {
// 6 We use onAppear to pass the tuples to our view model that will then do the
// the work of converting it into our Struct that our view would perfer.
model.updateResults(using: tupleListExample)
})
}
}
struct Example_Previews: PreviewProvider {
static var previews: some View {
Example()
}
}