I think the best solution (from both a code, accessibility and UI point of view) is to use the selection:
parameter of List
.
This has the main benefit of automatically highlighting the cell on tap, and handling the tap gesture states correctly (triggering the selection change only on touch up if the finger is still inside the cell):
import SwiftUI
struct Fruit: Hashable, Identifiable {
let id = UUID()
let name: String
}
struct ContentView: View {
let fruits = [
Fruit(name: "π Banana"),
Fruit(name: "π Apple"),
Fruit(name: "π Orange")
]
@State private var selectedFruit: Fruit.ID?
var body: some View {
NavigationStack {
List(fruits, selection: $selectedFruit) { fruit in
Text(fruit.name)
.foregroundStyle(Color(uiColor: .label))
.listRowBackground(fruit.id == selectedFruit ? Color(uiColor: .systemGray4) : nil)
}
.navigationTitle("Fruits")
}
.onChange(of: selectedFruit) { _, newValue in
if let newValue {
print("Touched \(fruits.first(where: { $0.id == newValue })!.name)")
}
}
}
}
We unfortunately need these 2 lines :
.foregroundStyle(Color(uiColor: .label))
.listRowBackground(fruit.id == selectedFruit ? Color(uiColor: .systemGray4) : nil)
Because otherwise the selected cell will use your app's tint color as its background when an hardware keyboard is connected.
If you want the cell do immediately lose its selected state after tap, you can modify .onChange
like so :
.onChange(of: selectedFruit) { _, newValue in
if let newValue {
print("Touched \(fruits.first(where: { $0.id == newValue })!.name)")
Task {
try? await Task.sleep(for: .milliseconds(80))
selectedFruit = nil
}
}
}
(adjust the delay as you see fit)
This is the final result:
.buttonStyle(.plain)
to disable the blue tint β Oui