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

如何在NSAttributedString中识别URL并替换为可点击链接?

实现方案

要把富文本里的所有链接替换成可点击的「URL」文本,咱们可以分步骤处理:

1. 识别文本里的所有URL

用NSDataDetector自动检测字符串中的链接,不管数量多少都能抓出来:

func extractURLs(from text: String) -> [NSRange] {
    let detector = try! NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue)
    let matches = detector.matches(in: text, options: [], range: NSRange(text.startIndex..., in: text))
    return matches.map { $0.range }
}

2. 替换链接文本并保留点击属性

这里要逆序处理链接位置,不然前面替换文本后,后面的链接位置会偏移出错。替换成「URL」后,给新文本加上对应原链接的点击属性:

func replaceURLsWithClickableLabel(in attributedString: NSAttributedString) -> NSAttributedString {
    let mutableString = NSMutableAttributedString(attributedString: attributedString)
    let originalText = mutableString.string
    
    // 逆序排序链接位置,避免替换后偏移
    let urlRanges = extractURLs(from: originalText).sorted { $0.location > $1.location }
    
    for range in urlRanges {
        // 提取原URL并转成可用的URL对象
        guard let urlStr = (originalText as NSString).substring(with: range).addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed),
              let targetURL = URL(string: urlStr) else { continue }
        
        // 把原链接文本替换成"URL"
        mutableString.replaceCharacters(in: range, with: "URL")
        
        // 给新的"URL"文本加上链接属性
        let newRange = NSRange(location: range.location, length: 3) // "URL"长度固定是3
        mutableString.addAttribute(.link, value: targetURL, range: newRange)
        // 可选:设置链接样式,比如蓝色下划线
        mutableString.addAttribute(.foregroundColor, value: UIColor.blue, range: newRange)
        mutableString.addAttribute(.underlineStyle, value: NSUnderlineStyle.single.rawValue, range: newRange)
    }
    
    return mutableString
}

3. 在控件中显示并处理点击

如果用UITextView,直接设置富文本,开启交互并通过代理处理点击:

// 原富文本
let originalAttrString = NSAttributedString(string: "hello https://www.stackoverflow.com and https://www.apple.com")
// 处理后的富文本
let processedAttrString = replaceURLsWithClickableLabel(in: originalAttrString)

// 配置UITextView
let textView = UITextView()
textView.attributedText = processedAttrString
textView.isUserInteractionEnabled = true
textView.dataDetectorTypes = [] // 禁用系统自动检测,避免冲突
textView.delegate = self

实现代理方法处理跳转:

extension YourViewController: UITextViewDelegate {
    func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
        UIApplication.shared.open(URL)
        return false
    }
}

如果用UILabel,需要加点击手势来处理链接:

// 配置UILabel
let label = UILabel()
label.attributedText = processedAttrString
label.isUserInteractionEnabled = true
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(onLabelTapped(_:)))
label.addGestureRecognizer(tapGesture)

// 处理点击事件
@objc func onLabelTapped(_ gesture: UITapGestureRecognizer) {
    guard let label = gesture.view as? UILabel, let attrText = label.attributedText else { return }
    let tapLocation = gesture.location(in: label)
    
    // 通过布局管理器定位点击的字符位置
    let layoutManager = NSLayoutManager()
    let textContainer = NSTextContainer(size: label.bounds.size)
    textContainer.lineFragmentPadding = 0
    textContainer.maximumNumberOfLines = label.numberOfLines
    textContainer.lineBreakMode = label.lineBreakMode
    
    layoutManager.addTextContainer(textContainer)
    let textStorage = NSTextStorage(attributedString: attrText)
    textStorage.addLayoutManager(layoutManager)
    
    let charIndex = layoutManager.characterIndex(for: tapLocation, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
    if charIndex < textStorage.length {
        let attrs = textStorage.attributes(at: charIndex, effectiveRange: nil)
        if let url = attrs[.link] as? URL {
            UIApplication.shared.open(url)
        }
    }
}

内容的提问来源于stack exchange,提问作者kunass2

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 14:16:46