Get SwiftUI Table single-clicked (column, row) pair on macOS

Get SwiftUI Table single-clicked (column, row) pair on macOS

When user single-clicks a SwiftUI Table on macOS, I'd like to know the clicked (column, row) pair, or point. I added onTapGesture() handler to the Table, but it's never invoked. How can I figure out which (column, row) was clicked?

My goal is to implement cell selection in my view model. I need to be able to determine the clicked (column, row) pair in order to select the correct cell in the view model.

Below is Apple example code, to which I added the ineffective onTapGesture() handler.

struct Person: Identifiable {
    let givenName: String
    let familyName: String
    let emailAddress: String
    let id = UUID()
}
struct PeopleTable: View {
    @State private var people = [
        Person(givenName: "Juan", familyName: "Chavez", emailAddress: "Read more"),
        Person(givenName: "Mei", familyName: "Chen", emailAddress: "[email protected]"),
        Person(givenName: "Tom", familyName: "Clark", emailAddress: "[email protected]"),
        Person(givenName: "Gita", familyName: "Kumar", emailAddress: "[email protected]")
    ]
    var body: some View {
        Table(people) {
            TableColumn("Given Name", value: \.givenName)
            TableColumn("Family Name", value: \.familyName)
            TableColumn("E-Mail Address", value: \.emailAddress)
        }
        .onTapGesture(perform: { p in
            print("clicked point \(p)")
        })
    }
}
#Preview {
    PeopleTable()
}

Answer

Pass a View to the TableColumns, and add the tap gesture to that.

Table(people) {
    TableColumn("Given Name") { person in
        Text(person.givenName)
            .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading)
            .contentShape(.rect)
            .onTapGesture {
                print("Clicked given name of \(person.id)")
            }
    }
    TableColumn("Family Name") { person in
        Text(person.familyName)
            .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading)
            .contentShape(.rect)
            .onTapGesture {
                print("Clicked family name of \(person.id)")
            }
    }
    TableColumn("E-Mail Address") { person in
        Text(person.emailAddress)
            .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading)
            .contentShape(.rect)
            .onTapGesture {
                print("Clicked email address of \(person.id)")
            }
    }
}

I have used frame and contentShape to increase the tappable as much as possible. Without those, the empty parts of the table cells are not tappable.

If this is still unsatisfactory, I would recommend wrapping a NSTableView with a NSViewRepresentable. You would set its action to a method, then access clickedColumn and clickedRow in that method.

Enjoyed this article?

Check out more content on our blog or follow us on social media.

Browse more articles