巴中市网站建设_网站建设公司_支付系统_seo优化
2025/12/20 17:15:49 网站建设 项目流程

一. 引言

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

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

比如下面这一段话 用户点击 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 在设计理念上的根本区别。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询