因 App 对标 WhatsApp,所以要完成文本音讯长按手机号或许链接时弹窗显现对应提示,并支撑仿制、翻开链接(url)。
先上 WhatsApp 作用图
长按手机号 | 长按链接 |
---|---|
代码完成
App 内的文本音讯内容展现时经过 UITextView 完成的,故给其增加一个 extension 来完成手机号、链接的辨认以及高亮作用。
enum TextLinkType {
case none
case link
case phoneNumber
}
extension UITextView {
// 辨认长按类型,并处理高亮
func longPressType(_ gesture: UILongPressGestureRecognizer) -> TextLinkType {
let location = gesture.location(in: self)
guard let textPosition = closestPosition(to: location) else { return .none }
if let range = tokenizer.rangeEnclosingPosition(textPosition, with: .word, inDirection: UITextDirection(rawValue: 1)),
let nsRange = convertToNSRange(textRange: range, textView: self) {
let isLinkInfo = isRangeInLink(nsRange, in: text)
if isLinkInfo.isLink, let range = isLinkInfo.matchRange {
highlightRange(range, in: self, with: UIColor(0x0A84FF, alpha:0.3))
showActionSheetForLinkType(isLinkInfo.linkType, link: substring(from: range, in: text) ?? "", range: range)
return isLinkInfo.linkType
}
}
return .none
}
// 处理文字显现色彩
private func highlightRange(_ range: NSRange, in textView: UITextView, with backgroundColor: UIColor, textColor: UIColor? = nil) {
let attributedString = NSMutableAttributedString(attributedString: textView.attributedText)
attributedString.addAttribute(NSAttributedString.Key.backgroundColor, value: backgroundColor, range: range)
if let textColorRef = textColor {
attributedString.addAttribute(NSAttributedString.Key.foregroundColor, value: textColorRef, range: range)
}
textView.attributedText = attributedString
}
private func substring(from nsRange: NSRange, in string: String) -> String? {
if let range = Range(nsRange, in: string) {
return String(string[range])
}
return nil
}
// 获取是否是链接,链接 range 以及 链接类型
private func isRangeInLink(_ range: NSRange, in string: String) -> (isLink: Bool, matchRange: NSRange?, linkType: TextLinkType) {
if let detector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue) {
// Check if the range is URL link
let matches = detector.matches(in: string, options: [], range: NSRange(location: 0, length: string.utf16.count))
for match in matches {
if NSIntersectionRange(range, match.range).length > 0 {
return (true, match.range, .link)
}
}
}
// Check if the range is a phone number
let detector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType.phoneNumber.rawValue)
if let matches = detector?.matches(in: text, options: [], range: range), !matches.isEmpty {
return (true, range, .phoneNumber)
}
return (false, nil, .none)
}
// Range 转换
private func convertToNSRange(textRange: UITextRange, textView: UITextView) -> NSRange? {
let beginning = textView.beginningOfDocument
let start = textRange.start
let end = textRange.end
let location = textView.offset(from: beginning, to: start)
let length = textView.offset(from: start, to: end)
return NSRange(location: location, length: length)
}
// 弹窗处理
private func showActionSheetForLinkType(_ linkType: TextLinkType, link: String, range: NSRange) {
// 依据链接类型显现长按弹窗
// 点击弹窗后铲除高亮
self.highlightRange(range, in: self, with: .clear)
}
}