I've been trying to sort out selections of items in a List, and first had to find the simplest possible example, which works. If you click on an item, it is highlighted, and the text box at the bottom shows the selected item.
struct ListTest1: View {
@State private var selection: String?
let names = [
"Cyril",
"Lana",
"Mallory",
"Sterling"
]
var body: some View {
VStack {
List(names, id: \.self, selection: $selection) { contact in
Text(contact)
}
Text("\(selection ?? "N/A")")
}
}
}
But when I try to use a simple custom view with a struct holding data, nothing works.
struct testData: Hashable {
var name = ""
var id = UUID()
}
struct ListTest2: View {
@State private var selection: testData?
let names = [
testData(name: "Cyril"),
testData(name: "Lana"),
testData(name: "Mallory"),
testData(name: "Sterling")
]
var body: some View {
VStack {
List(names, id: \.id, selection: $selection) { contact in
Text(contact.name)
}
Text("\((selection == nil) ? "N/A" : selection!.name)")
}
}
}
The item isn't highlighted or selected in the bottom text block. I'm sure there is a simple explanation but I don't understand enough about the internals to figure it out.
The selection
stores the ID
of the item -- not the item itself. The ID
is the default tag
given to the item in the List
This updated code works:
struct testData: Identifiable, Hashable {
var name = ""
var id = UUID()
}
struct ListTest2: View {
@State private var selection: testData.ID?
let names = [
testData(name: "Cyril"),
testData(name: "Lana"),
testData(name: "Mallory"),
testData(name: "Sterling")
]
var body: some View {
VStack {
List(names, selection: $selection) { contact in
Text(contact.name)
}
if let item = names.first(where: { $0.id == selection }) {
Text(item.name)
} else {
Text("N/A")
}
}
}
}
Or, if you really wanted the item itself, you could use tag
:
struct testData: Identifiable, Hashable {
var name = ""
var id = UUID()
}
struct ListTest2: View {
@State private var selection: testData?
let names = [
testData(name: "Cyril"),
testData(name: "Lana"),
testData(name: "Mallory"),
testData(name: "Sterling")
]
var body: some View {
VStack {
List(names, selection: $selection) { contact in
Text(contact.name).tag(contact)
}
if let selection {
Text(selection.name)
} else {
Text("N/A")
}
}
}
}
Note that on the second example, if you had a more complicated model and that model were mutable, if a property changed on the model, the selection would become invalid. By using Identifiable
, like the first example, you avoid that issue.