macOS SwiftUI Table with contextMenu

Using SwiftUI's new Table container, how can I add a context menu that appears when Control-clicking a row?

I can add the contextMenu modifier to the content of the TableColumn, but then I will have to add it to each individual column. And it only works above the specific text, not on the entire row:

I tried adding the modifier to the TableColumn itself, but it shows a compile error:

Value of type 'TableColumn<RowValue, Never, Text, Text>' has no member 'contextMenu'

Here's what I have in terms of source code, with the contextMenu modifier in the content of the TableColumn:

struct ContentView: View {

    @Environment(\.managedObjectContext) private var viewContext

    @FetchRequest(sortDescriptors: [NSSortDescriptor(keyPath: \Item.name, ascending: true)])
    private var items: FetchedResults<Item>

    @State
    private var sortOrder = [KeyPathComparator(\Item.name)]

    @State
    private var selection = Set<Item.ID>()

    var body: some View {
        NavigationView {
            Table(items, selection: $selection, sortOrder: $items.sortDescriptors) {
                TableColumn("Column 1") {
                    Text("Item at \($0.name!)")
                        .contextMenu {
                            Button(action: {}) { Text("Action 1") }
                            Divider()
                            Button(action: {}) { Text("Action 2") }
                            Button(action: {}) { Text("Action 3") }
                        }
                }

                TableColumn("Column 2") {
                    Text($0.id.debugDescription)
                }
            }
            .toolbar {
                ToolbarItem {
                    Button(action: addItem) {
                        Label("Add Item", systemImage: "plus")
                    }
                }
            }

            if selection.isEmpty {
                Text("Select an item")
            } else if selection.count == 1 {
                Text("Selected \(items.first(where: { $0.id == selection.first! })!.id.debugDescription)")
            } else {
                Text("Selected \(selection.count)")
            }
        }
    }
}

So, how can I add a context menu to the entire row inside the Table?

Replies

Did you try to use GeometryReader ?

  GeometryReader { geometry in
        NavigationView {
            Table(items, selection: $selection, sortOrder: $items.sortDescriptors) {
                TableColumn("Column 1") {
                    Text("Item at \($0.name!)")
                        .contextMenu {
                            Button(action: {}) { Text("Action 1") }
                            Divider()
                            Button(action: {}) { Text("Action 2") }
                            Button(action: {}) { Text("Action 3") }
                        }
                }

                TableColumn("Column 2") {
                    Text($0.id.debugDescription)
                }
            }
          .frame(width: geometry.size.width, height: geometry.size.height)    // Need to adapt the correct frame to the width of column1
  • @Claude31 It's unclear to me how changing the frame of the navigation view that the the table view is inside of in any way addresses the issue. It doesn't work. Why didn't you at least try it before posting it here?

Add a Comment

Here's how to ensure that the hitbox for the context menu extends across the entire cell for all cells in the table view:

Text("Item at \($0.name!)")
  .frame(
    maxWidth: .infinity,
    maxHeight: .infinity,
    alignment: .leading
  )
  .contentShape(Rectangle())
  .contextMenu {
    Button(action: {}) { Text("Action 1") }
    Divider()
    Button(action: {}) { Text("Action 2") }
    Button(action: {}) { Text("Action 3") }
  }

but then I will have to add it to each individual column

If you need to repeat the same functionality for the context menu, put that in function and make the Button action call that same function. Probably obvious to many. But seems to be the only work around if you want the context menu for the row to be consistent.

I too want a single context menu definition for a Table row.

In UIKit you have to select the defined cell to reveal the context menu, clicking on places which are effectively empty space doesn't open the context menu. The trick here is to expand your cell to fit the column width.

Out of the box Table { }.contextMenu{ } doesn't seem to work at all. I've attached the modifier to the Table and see no context menus at all.

So it seems we are stuck with attaching the context menu to the contents of the TableColumn and each column if you want it appear across the row.

It forces you to define the content closure, you can't use the TableColumn defaults.

Still feels like being an early adopter....