Home > Blockchain >  SwiftUI: Detect click on already selected list item
SwiftUI: Detect click on already selected list item

Time:10-06

I have a List view with several items. All of then serve navigation purposes and all are Text. I cannot use NavigationLinks 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
            }
        }
    }
 }
  • Related