当前位置: 首页 > news >正文

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 在设计理念上的根本区别。

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

http://www.jsqmd.com/news/115341/

相关文章:

  • AI Agent智能体是什么?和LLM关系是什么?
  • 前端部署更新后,如何优雅地通知用户刷新页面?收藏这篇就够了
  • Open-AutoGLM外卖自动化实战(从部署到上线的完整路径)
  • 揭秘Open-AutoGLM如何实现毫秒级快递轨迹更新:技术架构全解析
  • Open-AutoGLM物流信息同步全解析(业界首次公开架构细节)
  • 2025深圳|广州|东莞|惠州|珠海|佛山|中山|江门|肇庆|湛江|清远商业摄影培训机构推荐榜:陈阅视觉连续三年排名靠前 - 速递信息
  • 最近爆火的AI Agent究竟是什么?一文了解其背后的技术与潜力!
  • 【缺陷检测】图像处理检测PCB故障【含Matlab源码 14739期】
  • KiRequestDispatchInterrupt宏定义和nt!KiIpiServiceRoutine函数到hal!HalRequestSoftwareInterrupt
  • 电商比价不再难,手把手教你用Open-AutoGLM实现全自动利润挖掘
  • 2030年中国AI人才缺口或超400万!麦肯锡报告解析与大模型学习指南!
  • 软件测试环境建设与运维管控体系
  • 括号匹配问题
  • 2026年AI大模型学习攻略:从新手到专家,算法工程师的修炼手册!一篇文章掌握大模型与多模态奥秘!
  • (Open-AutoGLM性能优化秘籍):提升酒店数据抓取效率的7种方法
  • 还在手动点外卖?Open-AutoGLM让你每天省下30分钟,效率翻倍!
  • 年终总结资源合集
  • 回归测试策略与范围界定:构建可持续的软件质量防线‌
  • 前端安全性问题解决方案,零基础入门到精通,收藏这篇就够了
  • WPF利用Resx的多语言支持
  • 2025-2026 北京继承律师服务品质排行榜推荐:实战案例验证与权威机构口碑名单 - 老周说教育
  • 从数据采集到实时追踪,Open-AutoGLM全流程拆解,开发者必看
  • 2025上海装修公司优选:施工设计双优+业主高评价的五家盘点 - 资讯焦点
  • Kotlin资源合集
  • 探索式测试技巧与实战
  • 中国四通球阀制造厂家综合实力TOP10,市面上优质的四通球阀哪个好精选综合实力TOP企业 - 品牌推荐师
  • 【技术内幕】Open-AutoGLM如何实现毫秒级外卖订单生成?
  • 拒绝“狗熊掰棒子”!用 EWC (Elastic Weight Consolidation) 彻底终结 AI 的灾难性遗忘
  • Open-AutoGLM离线部署第一步:如何从Hugging Face稳定高速下载模型(完整教程)
  • 10个高效降AI率工具,MBA学生必备神器