日常开发中直接使用
NSDataDetector
匹配link
会遇到匹配不准确的问题,让人头痛不已比如:
1.link
中不包含scheme
;
2.link
中的scheme
/host
含有大写;
3.link
中包含中文字符;
4.link
尾部如果紧跟中文字符的话(很多用户会这么做),会被当作link
的一部分被匹配;以上这些情况都会导致点击
link
无效,影响用户体验,于是我参考微信的效果做了一些优化
错误示例:
错误示例
正确示例:
正确示例
代码示例:
语言:swift 5.0
组件:YYLabel
方法一:正则算法
参考:Swift5.0 用正则表达式检测文本中的网页链接
import UIKit
class YYLinkLabel: YYLabel {
func matchLinks() {
guard let text = attributedText, text.length > 0 else {
return
}
let regularString = "((http[s]{0,1}|ftp)://[a-zA-Z0-9\\.\\-]+\\.([a-zA-Z]{2,4})(:\\d+)?(/[a-zA-Z0-9\\.\\-~!@#$%^&*+?:_/=<>]*)?)|(www.[a-zA-Z0-9\\.\\-]+\\.([a-zA-Z]{2,4})(:\\d+)?(/[a-zA-Z0-9\\.\\-~!@#$%^&*+?:_/=<>]*)?)"
guard let regex = try? NSRegularExpression(pattern: regularString, options: .caseInsensitive) else {
return
}
let results = regex.matches(in: text.string, options: [], range: NSRange(location: 0, length: text.length))
if results.count > 0 {
for result in results {
let urlString = (text.string as NSString).substring(with: result.range)
// 对匹配到的内容设置高亮
(text as? NSMutableAttributedString)?.yy_setTextHighlight(result.range, color: UIColor.blue(), backgroundColor: .clear, tapAction: { (containerView, text, range, rect) in
HGPortal.transfer(from: nil, toURL: urlString)
})
}
}
}
}
方法二:在NSDataDetector
基础上做了一些优化
缺陷:不适合已经编码过的URL后面紧跟中文/其他特殊字符的情况,目前没有找到解决办法,不推荐使用。如果你有解决办法,欢迎评论区交流。
import UIKit
class YYLinkLabel: YYLabel {
func matchLinks() {
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
}
}