How to apply a context menu to SwiftUI Table row?
Asked Answered
G

3

8

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)

}

}
Gnostic answered 16/11, 2021 at 23:43 Comment(0)
C
6

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)
            }
        }
    }
Cailly answered 17/10, 2022 at 6:28 Comment(2)
Can you tell us where to download this sample Garden App from apple ?Enallage
The source code from apple is available here: developer.apple.com/documentation/swiftui/…Aloft
P
3

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 })
    }
}
Preachment answered 21/11, 2021 at 10:57 Comment(2)
Thank you for your reply, but this implementation can only work on the content of the cell, cannot completely cover the content of the whole line, and will not take effect for the blank part of the line.Gnostic
@Gnostic I agree, is there any better solution by now?Fungal
D
1

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"
        })
    }
Declivity answered 6/6 at 19:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.