You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 23:33:34