Enumerating Tuples In SwiftUI
ForEach with an Array of Tuples can cause some unexpected results, if your id that your using is not unique. Here is a story.
We can't find the internet
Attempting to reconnect
Something went wrong!
Hang in there while we get back on track
In this blog post, we’ll dive into the nuances of using ForEach
in SwiftUI, especially when dealing with tuples. Understanding how ForEach
works and the significance of unique identifiers is crucial for any SwiftUI developer.
ForEach
ForEach
in SwiftUI is a powerful tool used to iterate over a collection. It requires each item in the data set to have a unique id
. In this example, we use an integer as the id
, but be cautious — if the id
is not unique, issues may occur.
Here’s a straightforward example using a tuple list where id:\.0
represents the integer, providing a unique identifier:
struct Example: View {
var tupleListExample: [(Int, String)] = [
(1, "A"),
(2, "B"),
(3, "C"),
]
var body: some View {
VStack {
ForEach(tupleListExample, id:\.0) { item in
HStack {
Text("\(item.0)")
Spacer()
Text(item.1)
}
}
}
}
}
In this snippet, .0
and .1
reference the first and second elements of each tuple, respectively.
Using non-unique identifiers like Date()
can lead to unpredictable behavior. Below is an example where this approach sometimes works and sometimes doesn’t, due to the Date()
instances potentially initializing at the same time:
struct Example: View {
var tupleListExample: [(Date, String)] = [
(Date(), "A"),
(Date(), "B"),
(Date(), "C"),
]
var body: some View {
VStack {
ForEach(tupleListExample, id:\.0) { item in
HStack {
Text("\(item.0)")
Spacer()
Text(item.1)
}
}
}
}
}
This example illustrates the unreliability of Date()
as a unique identifier. Why is this? ForEach needs the id
to be unique within the collection. And if it contains the same date and even the same second it will behave in strange ways.
id
When looping through an array of tuples, a better approach is to convert them into a struct that conforms to the Identifiable
protocol, using UUID
for a truly unique id.
Here’s how you can convert a list of tuples into a struct:
import SwiftUI
import Combine
// Custom struct conforming to Identifiable
struct Foo: Identifiable {
var id: UUID = UUID()
var date: Date
var letter: String
}
// ViewModel for the view
class ExampleViewModel: ObservableObject {
@Published var results: [Foo] = []
func updateResults(using tuples: [(Date, String)]) {
results = tuples.map { Foo(date: $0.0, letter: $0.1) }
}
}
// The SwiftUI View
struct Example: View {
@StateObject private var model = ExampleViewModel()
var tupleListExample: [(Date, String)] = [
(Date(), "A"),
(Date(), "B"),
(Date(), "C"),
]
var body: some View {
VStack {
ForEach(model.results, id: \.id) { item in
HStack {
Text("\(item.date)")
Spacer()
Text(item.letter)
}
}
}.onAppear {
model.updateResults(using: tupleListExample)
}
}
}
struct Example_Previews: PreviewProvider {
static var previews: some View {
Example()
}
}
In this example, we create a Foo
struct conforming to Identifiable
, and a view model to handle the transformation of tuple data into this struct.
In conclusion, while tuples are handy in SwiftUI, ensuring unique identifiers is key to avoiding issues with ForEach
. Converting tuples into a struct with a UUID id is a reliable way to ensure uniqueness.
Feel free to explore further in the SwiftUI Documentation for more insights.