'SwiftUI: can a child view suppress its parent view's contextMenu?
I have a .contextMenu
on a large view in my SwiftUI app. Inside is a child view with an .onLongPressGesture
.
On iOS, context menus are triggered by a long press. So pressing the smaller view always triggers both the context menu and my own LongPressGesture
.
I'd like to stop my little view from triggering the parent's context menu.
struct ContentView: View {
@State var swap: Bool = false
var body: some View {
VStack {
Text("Press the smaller view to swap colors")
.padding()
.background { swap ? Color.blue : Color.red }
.onLongPressGesture { swap.toggle() }
Text("Press the larger view for context menu")
.padding()
}
.foregroundColor(.white)
.padding()
.background { swap ? Color.red : Color.blue }
.contextMenu { Text("Menu Goes Here") }
}
}
Things I've tried:
- Using
.highPriorityGesture
makes no difference - All the different
GestureMask
options affect subviews, not superviews - A
LongPressGesture
with a short duration ensures my custom gesture triggers first, but doesn't prevent the menu from appearing - A
DragGesture
with a zero minimum distance does the same as above - The solution outlined in SwiftUI: Cancel TapGesture on parent view involves 2 gestures that are both under the author's control, so they can pick a winner. The context menu's internals are opaque to me.
Is there a way for a subview to prevent gestures bubbling up to its superviews?
Solution 1:[1]
You can conditionally suppress the .contextMenu
by wrapping all its content in if
block, because an empty menu won't ever appear:
.contextMenu {
if !isPressingSmallerView {
Text("Menu Goes Here")
}
}
Then the trick to setting isPressingSmallerView
is to use .updating()
on our LongPressGesture
:
.gesture(
LongPressGesture()
.updating($isPressingSmallerView) { value, state, _ in
state = value
}
.onEnded { _ in
swap.toggle()
}
)
The complete solution in context:
struct ContentView: View {
@GestureState var isPressingSmallerView: Bool = false
@State var swap: Bool = false
var body: some View {
VStack {
Text("Press the smaller view to swap colors")
.padding()
.background { swap ? Color.blue : Color.red }
.gesture(
LongPressGesture()
.updating($isPressingSmallerView) { value, state, _ in
state = value
}
.onEnded { _ in
swap.toggle()
}
)
Text("Press the larger view for context menu")
.padding()
}
.foregroundColor(.white)
.padding()
.background { swap ? Color.red : Color.blue }
.contextMenu {
if !isPressingSmallerView {
Text("Menu Goes Here")
}
}
}
}
This is a small contrived example, so the @GestureState
variable is local to the ContentView
.
Depending on your view hierarchy, you may want to pass that state up from child to parent with a PreferenceKey
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 |