如何在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
相关产品推荐
相关产品推荐

