user1397892
user1397892

Reputation: 113

How to apply a context menu to SwiftUI Table row?

I found that the new table component of SwiftUI 3.0 is like a toy, which can be used easily, but it is difficult to expand more functions.

TableRow and TableColumn inherit from the value object. How can I get the view of a row? I want to set a different ContextMenu for each row. In addition, I want to set the ContextMenu for the column header.

How to implement it on the basis of Table component? I don't want to use the List component.

struct Person: Identifiable {

let givenName: String

let familyName: String

let id = UUID()

}

@State private var people = [

Person(givenName: "Juan", familyName: "Chavez"),

Person(givenName: "Mei", familyName: "Chen"),

Person(givenName: "Tom", familyName: "Clark"),

Person(givenName: "Gita", familyName: "Kumar"),

]

@State private var sortOrder = [KeyPathComparator(\Person.givenName)]

var body: some View {

Table(people, sortOrder: $sortOrder) {

TableColumn("Given Name", value: \.givenName)

TableColumn("Family Name", value: \.familyName)

}

.onChange(of: sortOrder) {

people.sort(using: $0)

}

}

Upvotes: 9

Views: 2408

Answers (3)

Todd
Todd

Reputation: 1953

It's an old question, but now it seems like this is easier to do. The below will show the uuid of the person right-clicked anywhere on the Table's row:

struct Person: Identifiable {
        let givenName: String
        var familyName: String
        let id = UUID()
    }
    
    @State private var people = [
        Person(givenName: "Juan", familyName: "Chavez"),
        Person(givenName: "Mei", familyName: "Chen"),
        Person(givenName: "Tom", familyName: "Clark"),
        Person(givenName: "Gita", familyName: "Kumar"),
    ]
    
    @State private var sortOrder = [KeyPathComparator(\Person.givenName)]
    
    var body: some View {
        
        Table(people, sortOrder: $sortOrder) {
            TableColumn("Given Name", value: \.givenName)
            TableColumn("Family Name", value: \.familyName)
        }.onChange(of: sortOrder) {
            
            people.sort(using: $0)
            
        }.contextMenu(forSelectionType: UUID.self, menu: { items in
            // The content of items will be the UUID of the selected people
            Button("\(items.first!.uuidString)") {
                dump(items)
            }
        }, primaryAction: { items in
            // Do something when the user double-clicks if you want to
            let i = (people.firstIndex(where: {aPerson in aPerson.id == items.first!}))!
            people[i].familyName = "Smith"
        })
    }

Upvotes: 2

Marc T.
Marc T.

Reputation: 5320

From macOS 13 this will work as expected:

enter image description here

To try it out use the Garden App from apple and replace the row section of the Table as below

  } rows: {
        
        ForEach(plants) { plant in
            TableRow(plant)
                .itemProvider { plant.itemProvider }
                .contextMenu {
                    Button {
                        
                    } label: {
                        Text("test")
                    }
                }
        }
        .onInsert(of: [Plant.draggableType]) { index, providers in
            Plant.fromItemProviders(providers) { plants in
                garden.plants.insert(contentsOf: plants, at: index)
            }
        }
    }

Upvotes: 6

AlbertUI
AlbertUI

Reputation: 1517

In order to have contextMenu working on SwiftUI 3.0 Table it is necessary to add it to every TableColumn item. Plus, if you want to add Double Tap support it is necessary to add it independently too.

Table(documents, selection: $fileSelection) {
    TableColumn("File name") { item in
        Text(item.filename)
            .contextMenu { YOUR_CONTEXT_MENU }
            .simultaneousGesture(TapGesture(count: 1).onEnded { fileSelection = item.id })
            .simultaneousGesture(TapGesture(count: 2).onEnded { YOUR_DOUBLE_TAP_IMPLEMENTATION })
    }
    TableColumn("Size (MB)") { item in
        Text(item.size)
            .contextMenu { YOUR_CONTEXT_MENU }
            .simultaneousGesture(TapGesture(count: 1).onEnded { fileSelection = item.id })
            .simultaneousGesture(TapGesture(count: 2).onEnded { YOUR_DOUBLE_TAP_IMPLEMENTATION })
    }
}

Upvotes: 3

Related Questions