Understanding gesture hierarchy
Learn how to set up the hierarchy of gestures in views and subviews within a SwiftUI app.
To handle gestures in a view that are affected by other gestures recognized by the view and its subviews, use GestureMask in SwiftUI.
.gesture(
DragGesture()
.onChanged { _ in
newColor = .orange
},
// Gesture mask
including: .all
)
You can apply a GestureMask as a parameter of the modifier gesture(_:including:).
struct ContentView: View {
@State private var newColor : Color = .blue
var body: some View {
Circle()
.fill(newColor)
.frame(width: 200, height: 200)
.gesture(
DragGesture()
.onChanged { _ in
newColor = .orange
},
including: .all
)
.shadow(
color: .primary,
radius: 1,
x: 3, y: 6
)
}
}
By default, a gesture is triggered only on the view where it is directly associated. Using the all value for the gesture mask, as an example, the gesture is triggered even if it starts in a subview or in non-visual parts of the hierarchy. We can have the following parameters:
.all: Enable the added gesture and all subviews and ancestors respond to the gesture..gesture: Enable the added gesture, but only the current view and not subviews or ancestors..subviews: Only subviews of the current view respond..none: Disable all gestures in the subview hierarchy, i.e. no views in the hierarchy respond to the gesture.
To allow a gesture to take precedence over others defined in the same view, use the highPriorityGesture(_:including:) modifier. In the example below, the LongPressGesture overrides the TapGesture.
struct ContentView: View {
@State private var message = "Tap the box"
var body: some View {
Rectangle()
.fill(Color.orange)
.frame(width: 200, height: 200)
.shadow(
color: .primary,
radius: 2,
x: 2, y: 4)
.overlay(
Text(message)
.foregroundColor(.white)
.bold()
)
.gesture(
TapGesture()
.onEnded {
message = "Tap detected"
}
)
.highPriorityGesture(
LongPressGesture()
.onEnded { _ in
message = "Long press priority"
}
)
}
}
Instead of being determined globally, the priority is set per view. So each view has its own set of priority gestures.
Some views and containers have associated gestures, such as swiping along the leading edge of the screen to navigate back. To instruct the system to give priority to the gestures you define instead of triggering system gestures, use the modifier defersSystemGestures(on:).
.defersSystemGestures(on: [.leading, .trailing])
In the example above, when dragging from the left or right edge using .defersSystemGestures(on:), the system waits for your view to decide whether to use the gesture. This can help by preventing system navigation gestures from being triggered immediately or for reasons specific to your application.
struct ContentView: View {
@State private var offset: CGSize = .zero
var body: some View {
Rectangle()
.fill(.blue)
.frame(width: 300, height: 300)
.offset(offset)
.gesture(
DragGesture()
.onChanged { gesture in
offset = gesture.translation
}
.onEnded { _ in
offset = .zero
}
)
.defersSystemGestures(on: [.leading, .trailing])
.edgesIgnoringSafeArea(.all)
}
}
Remember that .defersSystemGestures(on:) does not block the gesture, but gives priority to the view while the gesture is in progress.