news 2026/6/12 12:38:06

SwiftUI 如何精准识别用户点击的单词?一套可落地的实现方案

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SwiftUI 如何精准识别用户点击的单词?一套可落地的实现方案

一. 引言

在实际开发中,我们经常会遇到这样的需求:

  • 点击一段英文文本中的某个单词
  • 弹出释义 / 高亮该单词
  • 或者执行自定义逻辑(查词、收藏等)

比如下面这一段话 用户点击 finance,而不是整句话。

I have a passion for more academic achievement in finance.

在UIKit中这类需求并不陌生:

  • 使用 UITextView
  • 配合 NSAttributedString
  • 通过 shouldInteractWith URL 精准拿到点击的 range

但是问题来了。

在 SwiftUI 中,Text 并没有类似的回调,那我们要如何精准识别用户点击的是哪一个单词?

二.SwiftUI 的难点在哪里?

SwiftUI 的 Text 组件有几个天然限制:

  1. 没有点击 range 的回调
  2. onTapGesture 只能监听整体
  3. 没有内建的文本点击定位能力

也就是说,下面这种写法是不够的

Text(text) .onTapGesture { // 无法知道点的是哪个词 }

SwiftUI方便快捷,那么就也注定会失去一些灵活。不过我们仍然有办法来实现这个功能。

三. 实现方案

实现该方案的核心思路就是:把“点击文本” 转成 “URL事件”。

解决方案的关键在于这句话:

SwiftUI 可以识别 AttributedString 中的 link,并通过 openURL 回调暴露点击事件

拆解开来的话大概分为4个步骤:

  1. 使用正则表达式拆分出每个英文单词
  2. 给每个单词设置一个自定义 link
  3. 点击时通过 openURL 拦截
  4. 从 URL 中反推出被点击的单词

接下来我们就开始实际操作一下。

3.1 提取文本中英文单词

这里我们使用正则,而不是简单的 split(" "),原因是:

  • 可以正确处理标点
  • 不受空格数量影响
let pattern = "\\b[A-Za-z']+\\b"

使用正则来提取每一个单词。

3.2为每个单词设置可点击 link

我们先定义一个 ClickableText 组件:

struct ClickableText: View { let text: String let onWordTap: (String) -> Void }

然后通过正在来生成AttributedString:

private func makeAttributedText(from text: String) -> AttributedString { var attributed = AttributedString(text) let pattern = "\\b[A-Za-z']+\\b" let regex = try? NSRegularExpression(pattern: pattern) let nsText = text as NSString let range = NSRange(location: 0, length: nsText.length) regex?.enumerateMatches(in: text, range: range) { match, _, _ in guard let match else { return } let word = nsText.substring(with: match.range) if let range = Range(match.range, in: attributed) { attributed[range].link = URL(string: "word://\(word)") } } return attributed }

至此,每个单词都是一个可以点的效果了。

3.3拦截点击事件,识别被点的单词

接下来我们使用SwiftUI提供的openURL 环境值,来统一处理链接点击。

Text(attributed) .environment(\.openURL, OpenURLAction { url in guard url.scheme == "word", let word = url.host else { return .systemAction } onWordTap(word) return .handled })

至此我们就已经可以进准获取到用户点击的单词了。

四. 效果优化

虽然我们已经实现了点击效果,但是呢现在有个问题,文字我们并没有设置颜色,却发现变成了蓝色,而且就目前的情况来说,我们不知道当前已经点击的是哪个单词。

所以我们在上面的基础上,再优化两个点:

  1. 恢复文字颜色。
  2. 给点击单词添加高亮状态。

4.1去掉默认的蓝色样式

由于 link 默认会被当作系统链接处理,文字会变成蓝色。

但是解决方案非常简单:

Text(attributed) .tint(.primary)

tint 控制的是 link 的显示颜色,而不是文本本身。

效果如下:

4.2给选中的单词添加高亮效果

接下来我们引入一个选中状态:

@Binding var selectedWord: String?

当生成 AttributedString 时,根据状态决定是否高亮:

if selectedWord == word { attributed[range].backgroundColor = Color.red }

点击时更新状态:

onWordTap(word) selectedWord = word

最终完整代码如下:

struct ClickableText: View { let text: String let onWordTap: (String) -> Void @Binding var selectedWord: String? var body: some View { let attributed = makeAttributedText(from: text) ZStack { Text(attributed) .tint(.primary) // ✅ 1. 去掉蓝色,保持正文颜色 .environment(\.openURL, OpenURLAction { url in guard url.scheme == "word", let word = url.host else { return .systemAction } onWordTap(word) selectedWord = word return .handled }) } } private func makeAttributedText(from text: String) -> AttributedString { var attributed = AttributedString(text) let pattern = "\\b[A-Za-z']+\\b" guard let regex = try? NSRegularExpression(pattern: pattern) else { return attributed } let nsText = text as NSString let fullRange = NSRange(location: 0, length: nsText.length) regex.enumerateMatches(in: text, range: fullRange) { match, _, _ in guard let match = match else { return } let word = nsText.substring(with: match.range) if let range = Range(match.range, in: attributed) { attributed[range].link = URL(string: "word://\(word)") // ✅ 如果是当前选中的词,加高亮 if selectedWord == word { attributed[range].backgroundColor = Color.red } } } return attributed } }

五. 最终效果

我们在使用它时,只需要传递内容和选中内容。

struct ContentView: View { @State private var selectedWord: String? var body: some View { VStack { Text(selectedWord ?? "") .padding() .background(.thinMaterial) .cornerRadius(8) ClickableText(text: "I have a passion for more academic achievement in finance.", onWordTap: { word in selectedWord = word print("Tapped word: \(word)") }, selectedWord: $selectedWord) } .frame(maxWidth: .infinity, maxHeight: .infinity) .background(.yellow) } }

效果如下:

六. 结语

通过这篇文章,我们完整地实现了一个SwiftUI 中可精准识别用户点击单词的 Text 组件

整个过程并没有依赖 UIKit,也没有使用私有 API,而是基于 SwiftUI 官方提供的能力,逐步拆解并完成:

  • 使用 AttributedString 描述可交互文本
  • 通过 link 将“文本点击”转化为事件
  • 借助 openURL 精准拦截并识别被点击的单词
  • 利用状态驱动,实现选中高亮与样式控制

核心思想只有一句话:

在 SwiftUI 中,不再“计算用户点了哪里”,而是提前把可点击的内容建模成数据,再通过状态变化驱动 UI 更新。

这也是 SwiftUI 与 UIKit 在设计理念上的根本区别。

当然,识别文本点击并不只有这一种实现方式,实际项目中也可以根据复杂度选择不同方案,有其它方案的伙伴也欢迎交流。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/12 23:18:07

低代码资源合集

mksz824-真实高质量低代码商业项目,前端后端运维管理系统 文件大小: 27.0GB内容特色: 27GB全栈低代码商业项目源码与运维脚本适用人群: 想快速落地企业级系统的开发者核心价值: 拿来即用,省80%编码量,附上线部署手册下载链接: https://pan.q…

作者头像 李华
网站建设 2026/6/12 2:07:44

为什么你的Open-AutoGLM跑不起来?可能是requirements.txt少了这4个包

第一章:Open-AutoGLM requirements.txt 配置在构建 Open-AutoGLM 项目时,requirements.txt 文件是管理 Python 依赖的核心组件。该文件定义了项目运行所需的所有第三方库及其版本约束,确保开发、测试与生产环境的一致性。依赖项声明规范 所有…

作者头像 李华
网站建设 2026/6/11 18:21:17

为什么90%的比价项目都失败了?Open-AutoGLM避坑指南大公开

第一章:为什么90%的比价项目都失败了?在电商与价格敏感型应用激增的背景下,比价系统看似是技术实现中的“简单项目”。然而,实际落地时,超过九成的比价项目在6个月内停止维护或彻底失败。根本原因并非技术门槛过高&…

作者头像 李华
网站建设 2026/6/10 13:26:05

从零构建统计学核心:Python 实现 PDF、CDF 与逆向采样

这是一部关于如何从零构建统计学核心算法的深度技术指南。 为了真正达到“通俗易懂”且“内容详实”的要求,我将这篇内容扩展为六个核心章节。我们将不仅仅停留在代码层面,而是深入到数学直觉、算法原理、工程实现以及实际应用场景中。 我们将以书中的“…

作者头像 李华