SwiftUI 手势层级(Gesture Hierarchy)详解

彻底理解 GestureMask、highPriorityGesture 和 simultaneousGesture

在学习 SwiftUI 手势时,很多开发者都会遇到这些问题:

  • 为什么点击子 View,父 View 的 onTapGesture 没有执行?
  • GestureMask.gesture.subviews.all 到底是什么意思?
  • highPriorityGesturesimultaneousGesture 有什么区别?
  • 为什么 ScrollViewDragGesture 经常发生冲突?

这些问题都与 Gesture Hierarchy(手势层级) 有关。

本文通过大量示例,彻底理解 SwiftUI 的手势事件是如何传递的。


一、SwiftUI 的手势为什么会有层级?

假设有这样一个界面:

swift 复制代码
VStack {
    Rectangle()
}

对应的视图结构:

text 复制代码
VStack
└── Rectangle

如果父、子 View 都添加了点击手势:

swift 复制代码
VStack {
    Rectangle()
        .onTapGesture {
            print("Rectangle")
        }
}
.onTapGesture {
    print("VStack")
}

那么点击 Rectangle 时,到底是谁响应?

text 复制代码
Rectangle?
还是 VStack?
还是两个一起?

SwiftUI 默认有一套手势优先级规则。


二、默认规则:子 View 优先

运行下面代码:

swift 复制代码
import SwiftUI

struct ContentView: View {
    var body: some View {
        VStack {
            Rectangle()
                .fill(.blue)
                .frame(width: 200, height: 200)
                .onTapGesture {
                    print("Rectangle")
                }
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .background(.yellow)
        .onTapGesture {
            print("VStack")
        }
    }
}

界面如下:

text 复制代码
+----------------------+
|                      |
|    Rectangle         |
|                      |
+----------------------+

点击 Rectangle:

text 复制代码
👆

输出:

text 复制代码
Rectangle

而不是:

text 复制代码
VStack

为什么?

因为 SwiftUI 默认优先让距离用户最近的 View 处理手势。

事件的传递顺序可以理解为:

text 复制代码
用户点击
    │
    ▼
Rectangle
    │
    ▼
VStack

如果 Rectangle 已经成功识别手势,事件通常不会继续传递给 VStack。

如果点击黄色背景:

text 复制代码
👆

输出:

text 复制代码
VStack

因为这里只有父 View 可以响应。


三、GestureMask 到底是什么?

很多人第一次看到:

swift 复制代码
.gesture(
    TapGesture(),
    including: .gesture
)

都会疑惑:

这个 including 是什么意思?

其实:

including 用来指定 当前 Gesture 的作用范围(GestureMask)

它并不会修改手势类型,而是决定:

当前这个 Gesture 应该监听哪些 View。

SwiftUI 提供了四种 GestureMask:

swift 复制代码
.gesture
.subviews
.all
.none

下面分别介绍。


四、.gesture ------ 只监听当前 View

这是最容易误解的一个。

例如:

swift 复制代码
VStack {

    Rectangle()
        .fill(.blue)
        .frame(width: 200, height: 200)
        .onTapGesture {
            print("Rectangle")
        }

}
.gesture(
    TapGesture()
        .onEnded {
            print("VStack")
        },
    including: .gesture
)

很多人看到 .gesture,会误以为:

这是"普通 Gesture"。

其实不是。

它真正的意思是:

当前 View 自己参与手势识别,不把子 View 包含进来。

这里 Gesture 是添加到 VStack 上的,因此:

text 复制代码
VStack       ✅

Rectangle    ❌(不属于 VStack 这个 Gesture 的监听范围)

⚠️ 注意:

这里的 ❌ 并不是说 Rectangle 不能点击。

Rectangle 自己的 Gesture 仍然可以正常工作。

只是:

VStack 不会把 Rectangle 的区域也当成自己的 Gesture 区域。

因此:

点击 Rectangle:

text 复制代码
Rectangle

点击黄色背景:

text 复制代码
VStack

一句话总结:

.gesture = 当前 View,不包括子 View。


五、.subviews ------ 只监听子 View

修改代码:

swift 复制代码
VStack {

    Rectangle()
        .fill(.blue)
        .frame(width: 200, height: 200)

}
.gesture(
    TapGesture()
        .onEnded {
            print("VStack")
        },
    including: .subviews
)

此时:

text 复制代码
VStack      ❌

Rectangle   ✅

什么意思?

父 View 的 Gesture:

只监听子 View。

因此:

点击 Rectangle:

text 复制代码
VStack

点击黄色背景:

没有任何输出。

很多人第一次看到都会觉得奇怪。

其实就是:

text 复制代码
监听孩子

不监听自己

一句话总结:

.subviews = 只监听子 View。


六、.all ------ 当前 View 和子 View 都监听

改成:

swift 复制代码
.gesture(
    TapGesture()
        .onEnded {
            print("VStack")
        },
    including: .all
)

此时:

text 复制代码
VStack      ✅

Rectangle   ✅

无论点击:

text 复制代码
黄色背景

还是:

text 复制代码
Rectangle

VStack 的 Gesture 都能够参与识别。

可以理解成:

text 复制代码
整个 View 树
都是我的监听范围

一句话总结:

.all = 当前 View + 子 View。


七、.none ------ 不监听任何区域

swift 复制代码
.gesture(
    TapGesture(),
    including: .none
)

表示:

当前 Gesture 完全失效。

无论点击哪里:

都不会响应。

一句话总结:

.none = 禁用当前 Gesture。


八、一张图理解 GestureMask

假设:

text 复制代码
VStack
│
├── Rectangle
└── Circle

.gesture

text 复制代码
VStack      ✅

Rectangle   ❌

Circle      ❌

.subviews

text 复制代码
VStack      ❌

Rectangle   ✅

Circle      ✅

.all

text 复制代码
VStack      ✅

Rectangle   ✅

Circle      ✅

.none

text 复制代码
VStack      ❌

Rectangle   ❌

Circle      ❌

最终可以总结成下面这张表:

GestureMask 当前 View 子 View
.gesture
.subviews
.all
.none

注意:

这里的"当前 View"指的是:

谁调用了 .gesture(..., including:),谁就是当前 View。

它并不一定表示父 View。


九、highPriorityGesture()

默认情况下:

一个 View 可以拥有多个 Gesture。

例如:

swift 复制代码
Rectangle()
    .gesture(
        TapGesture()
            .onEnded {
                print("普通 Tap")
            }
    )
    .highPriorityGesture(
        TapGesture()
            .onEnded {
                print("高优先级 Tap")
            }
    )

点击:

输出:

text 复制代码
高优先级 Tap

因为:

text 复制代码
highPriorityGesture

        ↑

gesture

highPriorityGesture 的优先级更高。

一句话理解:

它会优先参与手势识别。


十、simultaneousGesture()

如果希望两个 Gesture 同时执行:

swift 复制代码
Rectangle()
    .gesture(
        TapGesture()
            .onEnded {
                print("普通")
            }
    )
    .simultaneousGesture(
        TapGesture()
            .onEnded {
                print("同时")
            }
    )

点击:

输出:

text 复制代码
普通

同时

两个 Gesture 都会执行。

一句话理解:

允许多个 Gesture 同时识别。


十一、三种 Gesture 的区别

可以用下面这张图来记忆:

text 复制代码
gesture()

↓

正常排队
text 复制代码
highPriorityGesture()

↓

插队
text 复制代码
simultaneousGesture()

↓

大家一起

十二、总结

SwiftUI 手势层级其实只有三个核心知识点。

① 默认情况下,子 View 优先于父 View。


② GestureMask 决定当前 Gesture 的监听范围。

GestureMask 作用
.gesture 只监听当前 View
.subviews 只监听子 View
.all 当前 View + 子 View
.none 不监听任何区域

③ 多个 Gesture 冲突时:

  • gesture():正常竞争
  • highPriorityGesture():提高优先级
  • simultaneousGesture():同时识别

掌握了这三个知识点,就能够理解绝大多数 SwiftUI 手势冲突问题,例如:

  • ScrollViewDragGesture
  • 图片缩放(MagnificationGesture)
  • 地图拖拽(Map)
  • NavigationStack 的返回手势
  • 自定义侧滑菜单

这些看似复杂的场景,本质上都是 Gesture Hierarchy 的应用。

相关推荐
不一样的少年_7 分钟前
不用框架,手搓 AI Agent:(一) 先让它跑起来
前端·后端·agent
味悲8 分钟前
浏览器解析机制与XSS的15种编码绕过
前端·xss
小牛itbull17 分钟前
【译】为什么我使用 React 重构了 WordPress?
前端·react.js·重构
用户0595401744617 分钟前
AI Agent 记忆存储踩坑实录:这个问题让我排查了 3 天,最终用 pytest + Docker 实现秒级回归
前端·css
今日无bug22 分钟前
Emmet 语法速成指南
前端·css·html
Hello.Reader25 分钟前
Tailwind CSS v4.3 从入门到实战Vite 安装、响应式布局、暗黑模式与主题配置
前端·css
kisshyshy30 分钟前
前端存储、表单异步提交与 this 指向完全指南
前端·javascript·html
cidy_9831 分钟前
Skills 技能库- JulyCode
前端
Csvn34 分钟前
TypeScript 泛型约束的 3 层演进:从 `extends` 到条件类型到重载推导
前端
小四的小六35 分钟前
AI产品翻车实录:模型总结幻觉的三大类型与检测方案
前端·openai·ai编程