How to Apply a Context Menu to Buttons in a Swiftui List Row

Adding contextMenu to a Button within a List prevents default Button functionality

Actually this is a question to Apple... meanwhile here is a possible solution. Tested with Xcode 11.4

List {
button
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading)
.contentShape(Rectangle())
.contextMenu {
button
}.onTapGesture {
self.on.toggle()
}
}

if you want highlight on row tap then it needs to apply custom ButtonStyle. For example you can consider solution in Custom Button in SwiftUI List

SwiftUI List selection doesn’t show If I add a NavigationLink and a .contextMenu to the list. Is this a known bug?

We can disable context menu button(s) for the moment of construction in edit mode (because the button is a reason of issue).

Here is a possible approach - some redesign is required to handle editMode inside context menu (see also comments inline).

Tested with Xcode 13.2 / iOS 15.2

demo

struct ContentViewSelection: View {
@State private var selection: String?
let names = ["Cyril", "Lana", "Mallory", "Sterling"]

var body: some View {
NavigationView {
List(names, id: \.self, selection: $selection) { name in
// separated view is needed to use editMode
// environment value
NameCell(name: name)
}
.toolbar {
EditButton()
}
}
}
}

struct NameCell: View {
@Environment(\.editMode) var editMode // << !!
let name: String

var body: some View {
NavigationLink(destination: Text("Hello, world!")) {
Text(name)
}
.contextMenu {
if editMode?.wrappedValue == .inactive { // << !!
Button(action: {}) {
Text("Tap me!")
}
}
}
}
}

SwiftUI, Core Data - Delete List Item with Context Menu

Instead of trying to derive an IndexSet from ForEach, which doesn't immediately expose one for you, you could create a separate delete method:

.contextMenu { Button(role: .destructive, action: { 
deleteExercise(exercise)
}) {
Label("Delete Exercise", systemImage: "trash")
} }
func deleteExercise(_ exercise: Exercise) { //I'm making an assumption that your model is called Exercise
withAnimation {
viewContext.delete(exercise)
viewContext.save()
}
}

In regards to your last question:

I am also wondering why I don't need to specify the IndexSet when using .onDelete

You don't need to specify it because it's sent as a parameter by onDelete -- that's what your deleteExercise(offsets:) is receiving from the onDelete modifier.

SwiftUI: Conditional Context Menu Shown Unexpectedly

Works fine with Xcode 14 / iOS 16

Here is possible workaround for older versions (it is possible to try different places for .id modifier to have appropriate, acceptable, UI feedback)

Tested with Xcode 13.4 / iOS 15.5

Text("Long press me. Editing: \((editMode?.wrappedValue == .active).description)")
.contextMenu(editMode?.wrappedValue == .active ? nil : contextMenu)
.id(editMode?.wrappedValue) // << this !!


Related Topics



Leave a reply



Submit