I have a List
view with several items. All of then serve navigation purposes and all are Text
. I cannot use NavigationLink
s for this app.
That said, I am monitoring the list selection through a @State
variable.
@State private var selection: String?
I print its value whever the view gets updated. It normally prints the unique id
of every item when selecting different items of the list.
The problem is when trying to select the same item again: in this case the view does not get updated, and I don't see the debug message in my console.
I would like to be able to press on a list item to display a view, and then be able to press on it again, despite it already being selected because I have logic I would like to run (for example: manual view refresh).
How can I detect the re-selection of an already selected list item?
For any questions, please let me know. Thank you for your help!
Update 1
This is the code I am using:
Helper list item struct:
struct NavItem {
let id: String
let text: String
}
List:
struct NavList: View {
@Binding private var selection: String?
private var listItems: [NavItem]
//note:
//selection is a @State variable declared in a parent struct and passed here.
//This is optional. I could declare it as a @state too.
init(listItems: [NavItem], selection: Binding<String?>) {
self.listItems = listItems
self._selection = selection
debugPrint(self.selection)
}
var body: some View {
List (selection: $selection) {
ForEach(listItems, id:\.id) { item in
Text(item.text)
.tag(item.id)
}
}
}
}
Note 1: I already know this problem can be approached by using the .onTapGesture
modifier. This solution seems to not be optimal. I tried seeking help for it here.
Note 2: In the project, the struct
that contains the list conforms to View
. When that view is displayed, it has the .listStyle(SidebarListStyle())
modifier added. This might be relevant.
CodePudding user response:
there are a number of ways to do what you want,
try this with .onTapGesture{..}
List (selection: $selection) {
ForEach(listItems, id: \.id) { item in
Text(item.text)
.tag(item.id)
.padding(10)
.listRowInsets(.init(top: 0,leading: 0, bottom: 0, trailing: 0))
.frame(minWidth: 111, idealWidth: .infinity, maxWidth: .infinity, alignment: .leading)
.onTapGesture {
print("----> " item.text)
}
}
}
or you can use a Button
instead of onTapGesture
;
List (selection: $selection) {
ForEach(listItems, id: \.id) { item in
Button(action: { print("----> " item.text) }) {
Text(item.text)
}
.padding(10)
.listRowInsets(.init(top: 0,leading: 0, bottom: 0, trailing: 0))
.tag(item.id)
.frame(minWidth: 111, idealWidth: .infinity, maxWidth: .infinity, alignment: .leading)
}
}
EDIT: full test code:
This is the code I used in my tests:
import SwiftUI
@main
struct TestApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
struct NavItem {
let id: String
let text: String
}
struct ContentView: View {
@State private var listItems: [NavItem] = [NavItem(id: "1", text: "1"),
NavItem(id: "2", text: "2"),
NavItem(id: "3", text: "3"),
NavItem(id: "4", text: "4")]
@State private var selection: String?
var body: some View {
List (selection: $selection) {
ForEach(listItems, id:\.id) { item in
Button(action: { print("----> " item.text) }) {
Text(item.text)
}
.padding(10)
.listRowInsets(.init(top: 0,leading: -5, bottom: 0, trailing: -5))
.frame(minWidth: 111, idealWidth: .infinity, maxWidth: .infinity, alignment: .leading)
.tag(item.id)
.background(Color.red) // <--- last modifier
}
}
}
}