iOS开发实战 - 精确匹配文本中的链接
2020-07-10 本文已影响0人
ArchLL
日常开发中经常会遇到对文本中链接匹配不准确的问题,让人头痛不已🐶
比如:
1.链接中包含中文字符;
2.链接尾部如果紧跟中文字符的话,也会被当作链接的一部分匹配进去;以上这些情况都会导致点击链接无效,降低用户的体验,
于是参考微信的匹配链接效果做了一些优化。
错误示例:
错误示例1 - 链接内部包含中文字符 错误示例2 - 链接尾部紧跟中文字符正确示例:
正确示例1 - 包含中文字符的链接不匹配 正确示例2 - 末尾的中文字符不匹配 正确示例3 - 实现精准匹配代码示例:
语言:swift 5.0
依赖:YYLabel
拓展:本文提供的思路完全可以应用在UITextView
、UIFieldView
以及其他一些第三方的文本组件中
import UIKit
class YYLinkLabel: YYLabel {
override var textColor: UIColor! {
didSet {
// 切换黑夜模式的时候需要去重新match link,否则link显示的颜色不正确
matchLink()
}
}
/// 匹配link
func matchLink() {
guard let text = attributedText, text.length > 0 else {
return
}
let regularRange = NSRange(location: 0, length: text.length)
guard let detector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue) else {
return
}
detector.enumerateMatches(in: text.string, options: [], range: regularRange) { (match, falgs, nil) in
// ⚠️ 如果原始链接中不包含scheme,如:`https://`,则转换成url后会自动加上前缀`http://`
guard let match = match, let url = match.url else {
return
}
// 对url进行解码
let urlString = url.absoluteString.removingPercentEncoding ?? ""
// 获取链接末尾中文字符的数量
let number = urlString.numberOfFootChineseCharactersCount()
// 获取已经移除链接末尾的中文字符的字符串
let realUrlString = urlString.stringOfRemoveFootChineseCharacters(number: number)
guard !realUrlString.isContainsChineseCharacters() else {
// 如果链接中含有中文字符,意味着这个链接无效,故不对其设置高亮
return
}
// ⚠️ 这里不能拿urlString/realUrlString去计算range,因为urlString可能已经加了scheme
let range = NSRange(location: match.range.location, length: match.range.length - number)
// 对匹配到的内容设置高亮
(text as? NSMutableAttributedString)?.yy_setTextHighlight(range, color: UIColor.blue(), backgroundColor: .clear, tapAction: { (containerView, text, range, rect) in
HGPortal.transfer(from: nil, toURL: realUrlString)
})
}
}
}
private extension String {
/// 是否包含中文字符(主要是为了判断链接中间是否包含中文字符)
func isContainsChineseCharacters() -> Bool {
var result = false
for (_, c) in enumerated() {
if isChineseCharacter(c) {
result = true
break
}
}
return result
}
/// 获取末尾中文字符的个数
func numberOfFootChineseCharactersCount() -> Int {
var number = 0
for (i, c) in reversed().enumerated() {
if isChineseCharacter(c) {
number = i + 1
} else {
break
}
}
return number
}
/// 得到一个已经去除末尾的中文字符的字符串
func stringOfRemoveFootChineseCharacters(number: Int) -> String {
guard number > 0 else {
return self
}
return String(prefix(utf16.count - number))
}
/// 判断是否是中文字符
private func isChineseCharacter(_ character: Character) -> Bool {
if "\u{4E00}" <= character && character <= "\u{9FA5}" {
return true
}
return false
}
}