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)
    }
}
相关推荐
茶底世界之下1 小时前
斜光下的旧自行车:用 Awaking 留一版复古胶片色
ios·app·产品
2501_916008895 小时前
iPhone 设备信息查看,用 KeyMob 看型号、序列号、电池健康和 UDID
android·ios·小程序·https·uni-app·iphone·webview
茶底世界之下1 天前
iPhone Duo 适配实战,Awaking 踩过的 8 个坑
ios·swiftui·swift
跨境数据猎手1 天前
HTTP 响应体编码与压缩:识别、解码
网络协议·http·ios
也非非也1 天前
iPhone 中国区 AI 落地:Apple 与阿里千问合作的技术观察
大数据·人工智能·ios·agent·iphone·智子
茶底世界之下1 天前
同一片泳池,清透还是奶油胶片?用 Awaking 做两版成片
ios
xbzb1 天前
Swift 对象存储建环实验 + Ceilometer 计量:两个收尾服务
swift·ring·实验·ceilometer·象存储·建环
2501_916008891 天前
iOS App 图标、启动屏和多语言搭配 方法,上架前使用KXBuild一次做好 App 图标
ide·vscode·ios·objective-c·个人开发·swift·敏捷流程
2501_915918411 天前
iOS 推送通知怎么实现,从 APNs p8 密钥到 iOS 真机收到第一条推送
vscode·ios·objective-c·个人开发·swift·敏捷流程
东坡肘子1 天前
快速消失的硬盘容量 -- 肘子的 Swift 周报 #160
人工智能·swiftui·swift