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)
    }
}
相关推荐
杂雾无尘40 分钟前
开发者必看,全面解析应用更新策略,让用户无法拒绝你的应用更新!
ios·xcode·swift
xiangzhihong82 小时前
使用Universal Links与Android App Links实现网页无缝跳转至应用
android·ios
Digitally4 小时前
如何将iPhone备份到Mac/MacBook
macos·ios·iphone
帅次4 小时前
【iOS设计模式】深入理解MVC架构 - 重构你的第一个App
ios·swiftui·objective-c·iphone·swift·safari·cocoapods
东坡肘子9 小时前
高温与奇怪的天象 | 肘子的 Swift 周报 #092
人工智能·swiftui·swift
Swift社区9 小时前
Swift 解 LeetCode 320:一行单词有多少种缩写可能?用回溯找全解
开发语言·leetcode·swift
Frank学习路上20 小时前
【IOS】XCode创建firstapp并运行(成为IOS开发者)
开发语言·学习·ios·cocoa·xcode
瓜子三百克1 天前
CALayer的异步处理
macos·ios·cocoa
吴Wu涛涛涛涛涛Tao1 天前
一步到位:用 Very Good CLI × Bloc × go_router 打好 Flutter 工程地基
flutter·ios
杂雾无尘1 天前
开发者必看:如何在 iOS 应用中完美实现动态自定义字体!
ios·swift·apple