Reputation: 165
I have a SwiftUI List
on iOS14.3 (Xcode12.3) and inside it's cells I want to have a Menu
popup up when the user is tapping on a button. Additionally these cells have an onTapGesture
and the problem is that this gesture is also triggered when the menu button is pressed.
Example:
List {
HStack {
Text("Cell content")
Spacer()
// Triggers also onTapGesture of cell
Menu(content: {
Button(action: { }) {
Text("Menu Item 1")
Image(systemName: "command")
}
Button(action: { }) {
Text("Menu Item 2")
Image(systemName: "option")
}
Button(action: { }) {
Text("Menu Item 3")
Image(systemName: "shift")
}
}) {
Image(systemName: "ellipsis")
.imageScale(.large)
.padding()
}
}
.background(Color(.systemBackground))
.onTapGesture {
print("Cell tapped")
}
}
If you tap on the ellipsis image, the menu opens but also "Cell tapped"
will be printed to the console. This is a problem for me, because in my real world example I am collapsing the cell within the tap gesture and of course I don't want that to happen when the user presses the menu button.
I found out, that when I long press on the button the gesture won't be triggered. But this is not acceptable UX in my opinion, took me a while to find that out for my self.
I also noticed, when I replace the Menu
with a regular Button
, then the cells gesture is not triggered while pressing (only with BorderlessButtonStyle
or PlainButtonStyle
, otherwise he is not active at all). But I don't know how to open a Menu from it's action.
List {
HStack {
Text("Cell content")
Spacer()
// Does not trigger cells onTapGesture, but how to open Menu from action?
Button(action: { print("Button tapped") }) {
Image(systemName: "ellipsis")
.imageScale(.large)
.padding()
}
.buttonStyle(BorderlessButtonStyle())
}
.background(Color(.systemBackground))
.onTapGesture {
print("Cell tapped")
}
}
Upvotes: 10
Views: 5411
Reputation: 54466
Alternatively, you can override Menu onTapGesture
:
struct ContentView: View {
var body: some View {
List {
HStack {
Text("Cell content")
Spacer()
Menu(content: {
Button(action: {}) {
Text("Menu Item 1")
Image(systemName: "command")
}
Button(action: {}) {
Text("Menu Item 2")
Image(systemName: "option")
}
Button(action: {}) {
Text("Menu Item 3")
Image(systemName: "shift")
}
}) {
Image(systemName: "ellipsis")
.imageScale(.large)
.padding()
}
.onTapGesture {} // override here
}
.contentShape(Rectangle())
.onTapGesture {
print("Cell tapped")
}
}
}
}
Also, there's no need to use .background(Color(.systemBackground))
to tap on spacers. This is not really flexible - what if you want change background color?
You can use contentShape
instead:
.contentShape(Rectangle())
Upvotes: 13
Reputation: 257693
I'm not sure if this is a bug or expected behavior, but instead of fighting with it I would recommend to avoid such overlapping (because even with success today it might stop working on different systems/updates).
Here is possible solution (tested with Xcode 12.1 / iOS 14.1)
List {
HStack {
// row content area
HStack {
Text("Cell content")
Spacer()
}
.background(Color(.systemBackground)) // for tap on spacer area
.onTapGesture {
print("Cell tapped")
}
// menu area
Menu(content: {
Button(action: { }) {
Text("Menu Item 1")
Image(systemName: "command")
}
Button(action: { }) {
Text("Menu Item 2")
Image(systemName: "option")
}
Button(action: { }) {
Text("Menu Item 3")
Image(systemName: "shift")
}
}) {
Image(systemName: "ellipsis")
.imageScale(.large)
.padding()
}
}
}
Upvotes: 3