日常開發(fā)中直接使用
NSDataDetector
匹配link
會遇到匹配不準(zhǔn)確的問題茵休,讓人頭痛不已比如:
1.link
中不包含scheme
;
2.link
中的scheme
/host
含有大寫浙垫;
3.link
中包含中文字符;
4.link
尾部如果緊跟中文字符的話(很多用戶會這么做),會被當(dāng)作link
的一部分被匹配崎弃;以上這些情況都會導(dǎo)致點(diǎn)擊
link
無效渤闷,影響用戶體驗(yàn)疾瓮,于是我參考微信的效果做了一些優(yōu)化
錯誤示例:
正確示例:
代碼示例:
語言:swift 5.0
組件:YYLabel
方法一:正則算法
參考:Swift5.0 用正則表達(dá)式檢測文本中的網(wǎng)頁鏈接
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)
// 對匹配到的內(nèi)容設(shè)置高亮
(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
基礎(chǔ)上做了一些優(yōu)化
缺陷:不適合已經(jīng)編碼過的URL后面緊跟中文/其他特殊字符的情況,目前沒有找到解決辦法飒箭,不推薦使用狼电。如果你有解決辦法,歡迎評論區(qū)交流弦蹂。
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://`,則轉(zhuǎn)換成url后會自動加上前綴`http://`
guard let match = match, let url = match.url else {
return
}
// 對url進(jìn)行解碼
let urlString = url.absoluteString.removingPercentEncoding ?? ""
// 獲取鏈接末尾中文字符的數(shù)量
let number = urlString.numberOfFootChineseCharactersCount()
// 獲取已經(jīng)移除鏈接末尾的中文字符的字符串
let realUrlString = urlString.stringOfRemoveFootChineseCharacters(number: number)
guard !realUrlString.isContainsChineseCharacters() else {
// 如果鏈接中含有中文字符凸椿,意味著這個鏈接無效削祈,故不對其設(shè)置高亮
return
}
// ?? 這里不能拿urlString/realUrlString去計(jì)算range,因?yàn)閡rlString可能已經(jīng)加了scheme
let range = NSRange(location: match.range.location, length: match.range.length - number)
// 對匹配到的內(nèi)容設(shè)置高亮
(text as? NSMutableAttributedString)?.yy_setTextHighlight(range, color: UIColor.blue(), backgroundColor: .clear, tapAction: { (containerView, text, range, rect) in
// 點(diǎn)擊跳轉(zhuǎn)
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
}
/// 獲取末尾中文字符的個數(shù)
func numberOfFootChineseCharactersCount() -> Int {
var number = 0
for (i, c) in reversed().enumerated() {
if isChineseCharacter(c) {
number = i + 1
} else {
break
}
}
return number
}
/// 得到一個已經(jīng)去除末尾的中文字符的字符串
func stringOfRemoveFootChineseCharacters(number: Int) -> String {
guard number > 0 else {
return self
}
return String(prefix(utf16.count - number))
}
/// 判斷是否是中文字符削饵,這里也可以使用正則算法優(yōu)化岩瘦,提高效率
private func isChineseCharacter(_ character: Character) -> Bool {
if "\u{4E00}" <= character && character <= "\u{9FA5}" {
return true
}
return false
}
}