iOS 支持文本消息识别链接及电话号码的长按复制(同 WhatsApp)

因 App 对标 WhatsApp,所以要实现文本消息长按手机号或者链接时弹窗显示对应提示,并支持复制、打开链接(url)。

先上 WhatsApp 效果图

长按手机号 长按链接

代码实现

App 内的文本消息内容展示时通过 UITextView 实现的,故给其添加一个 extension 来实现手机号、链接的识别以及高亮效果。

Swift 复制代码
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)
    }
}
相关推荐
AI2中文网5 小时前
别再说AppInventor2只能开发安卓了!苹果iOS现已支持!
android·ios·跨平台·苹果·appstore·app inventor 2·appinventor
咕噜签名分发冰淇淋5 小时前
苹果ipa应用安装包ios系统闪退问题
macos·ios·cocoa
wsxlgg5 小时前
IOS打包上传 出现 You do not have required contracts to perform an operation 的解决办法
ios
HarderCoder7 小时前
深入理解 SwiftUI 中的 @ViewBuilder:从语法糖到实战
swift
HarderCoder9 小时前
Swift 中的可调用类型:彻底搞懂 `callAsFunction`、`@dynamicCallable` 与 `@dynamicMemberLookup`
swift
CuiXg9 小时前
iOS XML 处理利器:CNXMLParser 与 CNXMLDocument 深度解析
ios·swift
HarderCoder10 小时前
Swift 中 Enum 与 Struct:如何为状态建模选择最合适的工具
swift
QING61811 小时前
使用扩展函数为 AppCompatTextView 提供了多段文本点击区域设置功能
android·kotlin·app
大熊猫侯佩12 小时前
韦爵爷闯荡 Swift 6 江湖:单例秘籍新解(上)
swift·编程语言·apple