SwiftUI中可点击子串实现问题:@用户标签点击功能优化
SwiftUI实现可点击@用户标签的最优方案
你的现有代码存在两个核心问题:
attributedText函数仅提取了@标签文本,完全丢弃了非标签内容,导致帖子只显示标签;- 使用HStack布局会强制所有文本块横向排列,无法自动处理换行,不符合常规文本排版逻辑。
下面提供两种更优的SwiftUI实现方案,既能保留完整文本,又能实现@标签的点击交互:
方案一:原生SwiftUI + AttributedString(推荐)
利用SwiftUI的AttributedString标记可点击的@标签,结合点击位置检测实现交互,完全贴合原生SwiftUI生态:
import SwiftUI // 自定义属性,用于标记@用户标签 struct UserTagAttribute: AttributedStringKey { static var name: String = "UserTag" } extension AttributedString { var userTag: Bool? { get { self[UserTagAttribute.self] } set { self[UserTagAttribute.self] = newValue } } } struct ClickableTagTextView: View { let content: String var body: some View { Text(processedAttributedString) .onTapGesture { location in handleTagTap(at: location) } .padding() } // 生成带标记的AttributedString private var processedAttributedString: AttributedString { var attrString = AttributedString(content) let tagRegex = try! NSRegularExpression(pattern: "@\\w+") let fullRange = NSRange(content.startIndex..<content.endIndex, in: content) for match in tagRegex.matches(in: content, range: fullRange) { guard let swiftRange = Range(match.range, in: attrString) else { continue } // 标记为可点击标签 attrString[swiftRange].userTag = true // 设置标签样式(可选) attrString[swiftRange].foregroundColor = .blue attrString[swiftRange].underlineStyle = .single } return attrString } // 检测点击位置是否命中@标签 private func handleTagTap(at location: CGPoint) { // 获取底层UITextView(SwiftUI的Text在iOS中会包装为UITextView) guard let textView = UIApplication.shared.windows.first? .rootViewController? .view .subviews .first(where: { $0 is UITextView }) as? UITextView else { return } let layoutManager = textView.layoutManager let charIndex = layoutManager.characterIndex( for: location, in: textView.textContainer, fractionOfDistanceBetweenInsertionPoints: nil ) guard charIndex < textView.textStorage.length else { return } // 检查点击位置是否带有UserTag属性 let attributes = textView.textStorage.attributes(at: charIndex, effectiveRange: nil) guard attributes[UserTagAttribute.name] != nil else { return } // 提取完整的@标签文本 if let tagRange = textView.text.range(of: "@\\w+", options: .regularExpression, range: text.startIndex..<text.endIndex) { let tagText = String(text[tagRange]) print("点击了标签:\(tagText)") // 这里添加跳转用户主页等业务逻辑 } } } // 预览 struct ClickableTagTextView_Previews: PreviewProvider { static var previews: some View { ClickableTagTextView(content: "hello @someUser and @otherUser, have a nice day!") } }
方案二:UIViewRepresentable包装UITextView
如果需要更精细的交互控制(比如多个标签的精准识别),可以用UITextView的代理实现:
import SwiftUI import UIKit struct TagTextView: UIViewRepresentable { let content: String var onTagTap: ((String) -> Void)? func makeUIView(context: Context) -> UITextView { let textView = UITextView() textView.isEditable = false textView.isSelectable = true textView.delegate = context.coordinator textView.attributedText = generateAttributedText() return textView } func updateUIView(_ uiView: UITextView, context: Context) { uiView.attributedText = generateAttributedText() } func makeCoordinator() -> Coordinator { Coordinator(self) } // 生成带URL属性的AttributedString(用URL模拟标签点击) private func generateAttributedText() -> NSAttributedString { let attrString = NSMutableAttributedString(string: content) let tagRegex = try! NSRegularExpression(pattern: "@\\w+") let fullRange = NSRange(content.startIndex..<content.endIndex, in: content) for match in tagRegex.matches(in: content, range: fullRange) { guard let swiftRange = Range(match.range, in: content) else { continue } let tagText = String(content[swiftRange]) // 给标签添加自定义URL,用于代理识别 attrString.addAttribute(.link, value: "user://\(tagText)", range: match.range) attrString.addAttribute(.foregroundColor, value: UIColor.blue, range: match.range) attrString.addAttribute(.underlineStyle, value: NSUnderlineStyle.single.rawValue, range: match.range) } return attrString } class Coordinator: NSObject, UITextViewDelegate { var parent: TagTextView init(_ parent: TagTextView) { self.parent = parent } func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool { if URL.scheme == "user" { let tagText = URL.lastPathComponent parent.onTagTap?(tagText) print("点击了标签:\(tagText)") return false // 阻止系统默认的URL跳转 } return true } } } // 使用示例 struct TagTextView_Previews: PreviewProvider { static var previews: some View { TagTextView(content: "hello @someUser and @otherUser") { tag in print("触发标签点击:\(tag)") } .padding() } }
方案对比:
- 方案一更贴近SwiftUI原生写法,无需额外包装UIKit组件,但需要处理底层视图的获取;
- 方案二利用UITextView的成熟代理机制,标签识别更精准,适合复杂文本场景。
内容的提问来源于stack exchange,提问作者Noah Iarrobino
相关产品推荐
相关产品推荐

