在 SwiftUI 中,手势优先级管理提供了多种方式来处理视图上的手势冲突和组合。这些方法在多个手势作用于同一视图时,定义手势的优先级及响应策略。以下是 SwiftUI 手势优先级管理的主要方法:
1、默认优先级 (gesture())
描述:
gesture() 是默认的手势绑定方法,多个手势会在不冲突的情况下正常工作。
如果手势冲突,SwiftUI 会根据默认规则决定哪个手势响应。
示例:
Text("Hello, World!")
.gesture(
DragGesture()
.onChanged { value in
print("Drag detected")
}
)

2、高优先级手势 (highPriorityGesture())
描述:
提升某个手势的优先级,优先于默认手势响应。
如果视图绑定了多个手势,高优先级手势会抢占响应机会。
用法:
struct ContentView: View {
var body: some View {
List {
ForEach(0..<20) { index in
Text("Item \(index)")
.onTapGesture {
print("Item \(index) tapped!")
}
.highPriorityGesture( // 直接应用于 View
TapGesture()
.onEnded {
print("High-priority tap on \(index)")
}
)
}
}
}
}

效果:
快速点击某个选项,会先触发 highPriorityGesture 的 TapGesture,输出 “High-priority tap on X”。
如果没有设置 highPriorityGesture,快速点击可能会触发系统的滚动手势。
3、同时触发手势 (simultaneousGesture())
描述:
允许多个手势同时响应,而不会互相抢占响应机会。
用法:
struct ContentView: View {
var body: some View {
let tapGesture1 = TapGesture()
.onEnded {
print("Tap gesture 1 triggered")
}
let tapGesture2 = TapGesture()
.onEnded { _ in
print("Tap gesture 2 triggered")
}
Circle()
.fill(.blue)
.frame(width: 100, height: 100)
.gesture(
tapGesture1.simultaneously(with: tapGesture2)
)
}
}

效果:
两个点击手势会同时触发。
4、独占优先级手势 (exclusively(before:))
描述:
强制一个手势在另一个手势之前独占触摸事件。
如果前置手势未触发,后续手势才有机会响应。
用法:
struct ContentView: View {
var body: some View {
let tapGesture = TapGesture()
.onEnded {
print("Tap gesture triggered")
}
let longPressGesture = LongPressGesture()
.onEnded { _ in
print("Long press gesture triggered")
}
Circle()
.fill(.blue)
.frame(width: 100, height: 100)
.gesture(
tapGesture.exclusively(before: longPressGesture) // 点击优先,长按次之
)
}
}

效果:
如果快速单击圆形,触发 tapGesture,打印 “Tap gesture triggered”。
如果点击失败(例如未完全松开),则触发 longPressGesture,打印 “Long press gesture triggered”。
5、执行顺序手势
描述:
组合两个手势时,用户必须按特定的顺序完成第一个手势,然后才能触发第二个手势。
语法:
gesture1.sequenced(before: gesture2)
gesture1: 用户必须完成的第一个手势。
gesture2: 用户在完成第一个手势后可以执行的第二个手势。
用法:
struct ContentView: View {
@State private var offset = CGSize.zero
@State private var isDragging = false
var body: some View {
// 定义一个长按手势
let pressGesture = LongPressGesture()
.onEnded { _ in
withAnimation {
isDragging = true
}
}
// 定义一个拖拽手势
let dragGesture = DragGesture()
.onChanged { value in
offset = value.translation
}
.onEnded { _ in
withAnimation {
offset = .zero
isDragging = false
}
}
// 将长按手势与拖拽手势组合
let combinedGesture = pressGesture.sequenced(before: dragGesture)
// 一个可拖动的圆
return Circle()
.fill(.blue)
.frame(width: 100, height: 100)
.offset(offset)
.scaleEffect(isDragging ? 1.5 : 1)
.gesture(combinedGesture)
}
}

效果:
用户必须按顺序完成手势,比如:
先完成一个长按(LongPressGesture)。
然后再拖动(DragGesture)。
如果第一个手势未完成,第二个手势不会被识别。
6、实时反馈手势
描述:
在手势的每个更新阶段持续提供实时反馈。
用法:
struct ContentView: View {
@GestureState private var dragOffset = CGSize.zero // 临时状态,用于实时反馈
@State private var position = CGSize.zero // 最终位置,用于手势结束后的状态
var body: some View {
Circle()
.fill(.red)
.frame(width: 100, height: 100)
.offset(x: position.width + dragOffset.width,
y: position.height + dragOffset.height)
.gesture(
DragGesture()
.updating($dragOffset) { value, state, _ in
state = value.translation // 实时更新 dragOffset
}
.onEnded { value in
// 手势结束后,更新最终位置
position.width += value.translation.width
position.height += value.translation.height
}
)
}
}

效果:
拖动圆形时,dragOffset 会实时更新,控制视图的位置偏移。
手势结束时,将 dragOffset 的值累加到 position,并重置 dragOffset。
关键点:
.updating($dragOffset) { value, state, transaction in
state = value.translation
}
value: 当前的拖拽手势数据 (DragGesture.Value),包括位置信息、速度等。
state: 一个绑定变量,用于存储拖拽过程中的实时状态。这里通过 $dragOffset 绑定到外部的 @GestureState 变量。
transaction: 与动画相关的参数(通常不需要在简单实现中使用)。
过程:
拖拽中,value.translation 表示当前的拖拽偏移量 (CGSize 类型)。
每当手势更新时,state 会实时接收偏移量并更新 UI。
7、禁用手势
描述:
用于控制视图是否响应用户交互的修饰符。
用法:
struct ContentView: View {
@State private var isDisabled = false
var body: some View {
VStack {
Button("Disable Circle Interaction") {
isDisabled.toggle()
}
Circle()
.fill(isDisabled ? Color.gray : Color.blue)
.frame(width: 100, height: 100)
.onTapGesture {
print("Circle tapped!")
}
.allowsHitTesting(!isDisabled) // 根据 isDisabled 设置交互
}
}
}

效果:
1、当 allowsHitTesting(false) 时:
Circle 不响应任何点击或拖动事件。
但 Circle 仍然显示在界面上。
2、当 allowsHitTesting(true) 时:
Circle 恢复点击交互功能。
常见问题
allowsHitTesting(false) 会让视图忽略交互,但不会隐藏该视图。
它不会影响子视图的交互行为,子视图仍然可以接收手势事件,除非对子视图也单独设置了 allowsHitTesting(false)。
如果想彻底禁用某个视图及其子视图的交互,可以结合 disabled(true) 使用。
优先级关系总结
1、gesture():
默认优先级,多个手势根据系统规则决定冲突的响应。
2、highPriorityGesture():
设置手势的最高优先级,抢占默认手势。
高优先级手势会忽略低优先级手势。
3、simultaneousGesture():
同时触发,允许多个手势响应。
4、exclusively(before:):
一个手势必须独占触摸事件,如果未触发,才允许后续手势响应。
5、sequenced(before:)
第一个手势必须完成,才能触发第二个手势。
6、updating(_:)
提供实时更新状态,手势交互过程中动态调整视图的外观或位置。
7、allowsHitTesting()
用于控制视图是否响应用户交互的修饰符。
使用建议
高优先级操作:如果某些手势是核心功能,使用 highPriorityGesture() 确保其优先响应。
组合手势:当需要同时触发多个手势时,使用 simultaneousGesture()。
冲突管理:如果两个手势有潜在冲突,使用 exclusively(before:) 确定优先处理逻辑。
默认行为:对于简单手势,直接使用 gesture()。
