SwiftUI手势优先级管理方法
SwiftUI手势优先级管理方法

SwiftUI手势优先级管理方法

在 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()。

   

如果您认为这篇文章给您带来了帮助,您可以在此通过支付宝或者微信打赏网站开发者。

欢迎加入我们的 微信交流群QQ交流群,交流更多精彩内容!
微信交流群二维码 QQ交流群二维码

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注