SwiftUI中实现话题标签(#)高亮及可点击功能需求
在SwiftUI中识别并处理话题标签(#开头)的实现方案
要实现识别文本中#开头的话题标签并添加黄色高亮+点击响应,同时兼容已有的链接处理逻辑,可以通过AttributedString结合自定义属性和点击位置判断来完成,具体步骤如下:
1. 定义正则匹配规则
先分别定义匹配URL和话题标签的正则表达式,确保两种类型的内容都能被正确识别:
// 匹配标准URL(可根据需求调整正则) private let urlRegex = #"(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?"# // 匹配#开头的话题标签(支持字母、数字、下划线) private let hashtagRegex = #"#\w+"#
2. 编写文本转换工具函数
创建一个函数,将原始字符串转换成带有样式和自定义属性的AttributedString,同时处理链接和话题标签:
func convertToStyledAttributedString(from text: String) -> AttributedString { var attributedString = AttributedString(text) // 处理话题标签 if let hashtagMatches = try? NSRegularExpression(pattern: hashtagRegex, options: .caseInsensitive) { let range = NSRange(attributedString.startIndex..., in: attributedString) hashtagMatches.enumerateMatches(in: text, options: [], range: range) { match, _, _ in guard let matchRange = match?.range else { return } if let attributedRange = Range(matchRange, in: attributedString) { // 设置黄色高亮背景 attributedString[attributedRange].backgroundColor = .yellow // 添加自定义属性,标记为话题标签 attributedString[attributedRange].customAttributes = ["type": "hashtag"] } } } // 处理链接(兼容原有逻辑) if let urlMatches = try? NSRegularExpression(pattern: urlRegex, options: .caseInsensitive) { let range = NSRegularExpression.Range(attributedString.startIndex..., in: attributedString) urlMatches.enumerateMatches(in: text, options: [], range: range) { match, _, _ in guard let matchRange = match?.range else { return } if let attributedRange = Range(matchRange, in: attributedString) { // 设置蓝色文本 attributedString[attributedRange].foregroundColor = .blue // 添加自定义属性,标记为链接 attributedString[attributedRange].customAttributes = ["type": "url"] } } } return attributedString } // 给AttributedString添加自定义属性的扩展 extension AttributedString { private static let customAttributesKey = AttributeName("customAttributes") var customAttributes: [String: String] { get { self[.customAttributesKey] as? [String: String] ?? [:] } set { self[.customAttributesKey] = newValue } } }
3. 实现可点击的Text视图
使用生成的AttributedString创建Text,并通过onTapGesture结合点击位置判断,区分点击的是链接还是话题标签:
struct StyledTextView: View { let text: String var body: some View { Text(convertToStyledAttributedString(from: text)) .onTapGesture { location in guard let textView = NSApp.keyWindow?.contentView?.subviews.first(where: { $0 is NSTextView }) as? NSTextView else { return } let layoutManager = textView.layoutManager let characterIndex = layoutManager.characterIndex(for: location, in: textView.textContainer, fractionOfDistanceBetweenInsertionPoints: nil) guard let attributedString = textView.attributedString() as? AttributedString, characterIndex < attributedString.count else { return } let attributes = attributedString[attributedString.index(attributedString.startIndex, offsetBy: characterIndex)].customAttributes switch attributes["type"] { case "hashtag": // 获取点击的话题标签内容 let hashtagRange = findRangeOfAttribute(in: attributedString, at: characterIndex, attributeKey: .customAttributesKey) if let range = hashtagRange { let hashtag = String(attributedString[range]) print("点击了话题标签:\(hashtag)") // 这里添加你的话题标签点击逻辑,比如跳转、弹窗等 } case "url": // 获取点击的链接内容 let urlRange = findRangeOfAttribute(in: attributedString, at: characterIndex, attributeKey: .customAttributesKey) if let range = urlRange { let urlString = String(attributedString[range]) print("点击了链接:\(urlString)") // 这里添加你的链接跳转逻辑 } default: break } } } // 辅助函数:根据位置找到对应属性的完整范围 private func findRangeOfAttribute(in attributedString: AttributedString, at index: Int, attributeKey: AttributeName) -> Range<AttributedString.Index>? { var currentIndex = attributedString.index(attributedString.startIndex, offsetBy: index) var startIndex = currentIndex var endIndex = currentIndex // 向左查找属性起始位置 while startIndex > attributedString.startIndex { let prevIndex = attributedString.index(before: startIndex) if attributedString[prevIndex][attributeKey] != attributedString[currentIndex][attributeKey] { break } startIndex = prevIndex } // 向右查找属性结束位置 while endIndex < attributedString.endIndex { let nextIndex = attributedString.index(after: endIndex) if attributedString[nextIndex][attributeKey] != attributedString[currentIndex][attributeKey] { break } endIndex = nextIndex } return startIndex...endIndex } }
使用示例
在你的视图中直接调用这个自定义视图即可:
struct ContentView: View { let sampleText = "欢迎访问我的博客https://example.com,一起讨论#SwiftUI和#iOS开发!" var body: some View { StyledTextView(text: sampleText) .padding() } }
关键说明
- 自定义属性:通过给
AttributedString添加自定义属性,我们可以在点击时快速判断当前内容的类型(链接/话题标签)。 - 点击位置判断:利用
NSTextView的layoutManager获取点击对应的字符索引,再通过遍历找到该属性的完整范围,从而拿到完整的话题标签或链接内容。 - 样式兼容:话题标签使用黄色背景高亮,链接保持原有蓝色文本,两种样式互不干扰。
内容的提问来源于stack exchange,提问作者Ahmed Zaidan
相关产品推荐
相关产品推荐

