You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何匹配手机通讯录与Firebase注册联系人并在UITableviewController展示共同联系人

实现两个列表的共同联系人匹配并在UITableView展示

要搞定这个需求,核心是统一号码格式(不然格式不同的同个号码匹配不上),然后获取两个数据源、找出交集,最后展示到TableView里。我给你一步步拆解:

1. 先统一电话号码格式

不管是Firebase里的号码(可能带+86、空格),还是手机通讯录里的(可能带括号、短横线),都要转换成完全一致的格式,比如+86138xxxxxxx这种纯数字加统一国家码的形式。写个工具函数来处理:

import Foundation

func formatPhoneNumber(_ rawNumber: String) -> String {
    // 先去掉所有非数字的字符(空格、括号、短横线这些)
    let onlyDigits = rawNumber.components(separatedBy: CharacterSet.decimalDigits.inverted).joined()
    
    // 处理国家码:假设默认是+86,如果你有海外用户,可以改成动态获取系统地区的国家码
    switch onlyDigits.prefix(2) {
    case "86":
        return "+86\(onlyDigits.dropFirst(2))"
    case "+8":
        return onlyDigits // 已经带+86的情况
    default:
        return "+86\(onlyDigits)" // 不带国家码的,补上+86
    }
}

2. 获取两个数据源

从Firebase获取注册联系人

假设你用的是Firestore,先定义个模型存Firebase返回的联系人,再写获取逻辑:

import FirebaseFirestore

// Firebase联系人模型
struct FirebaseContact {
    let phoneNumber: String
    let name: String? // 如果Firebase里存了姓名就用,没有就后续从通讯录取
}

var firebaseContacts: [FirebaseContact] = []

func fetchFirebaseContacts() {
    let db = Firestore.firestore()
    db.collection("your_contacts_collection_name").getDocuments { snapshot, error in
        if let error = error {
            print("拉取Firebase联系人失败:\(error.localizedDescription)")
            return
        }
        guard let docs = snapshot?.documents else { return }
        
        firebaseContacts = docs.compactMap { doc in
            guard let phone = doc["phoneNumber"] as? String else { return nil }
            return FirebaseContact(phoneNumber: phone, name: doc["name"] as? String)
        }
    }
}

获取手机通讯录

需要用到Contacts框架,记得先在Info.plist里添加NSContactsUsageDescription(告诉用户为什么要访问通讯录):

import Contacts

var phoneContacts: [CNContact] = []

func fetchPhoneContacts(completion: @escaping () -> Void) {
    let contactStore = CNContactStore()
    
    // 先请求权限
    contactStore.requestAccess(for: .contacts) { granted, error in
        if let error = error {
            print("通讯录权限请求失败:\(error.localizedDescription)")
            completion()
            return
        }
        guard granted else {
            print("用户拒绝了通讯录权限")
            completion()
            return
        }
        
        // 定义要获取的字段:姓名、电话号码
        let requiredKeys = [CNContactGivenNameKey, CNContactFamilyNameKey, CNContactPhoneNumbersKey] as [CNKeyDescriptor]
        let fetchRequest = CNContactFetchRequest(keysToFetch: requiredKeys)
        
        do {
            try contactStore.enumerateContacts(with: fetchRequest) { contact, _ in
                self.phoneContacts.append(contact)
            }
        } catch {
            print("获取通讯录失败:\(error.localizedDescription)")
        }
        
        completion()
    }
}

3. 找出共同联系人

用Set来存Firebase的格式化号码,这样匹配效率更高(O(1)查找),然后遍历通讯录找匹配项:

// 共同联系人模型,用来展示
struct CommonContact {
    let name: String
    let phoneNumber: String
}

var commonContacts: [CommonContact] = []

func findMatchingContacts() {
    // 确保两个数据源都已加载完成
    guard !firebaseContacts.isEmpty, !phoneContacts.isEmpty else { return }
    
    // 把Firebase的号码格式化后存入Set
    let firebasePhoneSet = Set(firebaseContacts.map { formatPhoneNumber($0.phoneNumber) })
    
    // 遍历通讯录找匹配
    for contact in phoneContacts {
        // 一个联系人可能有多个号码,逐个检查
        for phoneEntry in contact.phoneNumbers {
            let formattedPhone = formatPhoneNumber(phoneEntry.value.stringValue)
            
            if firebasePhoneSet.contains(formattedPhone) {
                // 拼接姓名,空的话显示"未知姓名"
                let fullName = "\(contact.givenName) \(contact.familyName)".trimmingCharacters(in: .whitespaces)
                let displayName = fullName.isEmpty ? "未知姓名" : fullName
                
                // 避免重复添加同一个号码的联系人
                if !commonContacts.contains(where: { $0.phoneNumber == formattedPhone }) {
                    commonContacts.append(CommonContact(name: displayName, phoneNumber: formattedPhone))
                }
            }
        }
    }
    
    // 回到主线程刷新TableView
    DispatchQueue.main.async {
        self.tableView.reloadData()
    }
}

4. 在UITableView里展示

实现TableView的数据源方法就行,记得先给Cell注册标识符(比如CommonContactCell):

override func viewDidLoad() {
    super.viewDidLoad()
    
    // 注册Cell
    tableView.register(UITableViewCell.self, forCellReuseIdentifier: "CommonContactCell")
    
    // 用DispatchGroup等待两个数据源都加载完成
    let group = DispatchGroup()
    
    group.enter()
    fetchFirebaseContacts()
    group.leave()
    
    group.enter()
    fetchPhoneContacts {
        group.leave()
    }
    
    // 两个任务都完成后找共同联系人
    group.notify(queue: .main) {
        self.findMatchingContacts()
    }
}

// MARK: - UITableView DataSource
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return commonContacts.count
}

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "CommonContactCell", for: indexPath)
    let contact = commonContacts[indexPath.row]
    
    cell.textLabel?.text = contact.name
    cell.detailTextLabel?.text = contact.phoneNumber
    return cell
}

一些注意事项

  • 权限处理:一定要先请求通讯录权限,不然会崩溃或者拿不到数据
  • 异步处理:用DispatchGroup确保两个数据源都加载完成再匹配,避免空数据
  • 号码格式兼容:如果有海外用户,要动态获取系统的国家码,不要硬写+86
  • 去重:同一个联系人可能有多个号码匹配Firebase,要避免重复添加

内容的提问来源于stack exchange,提问作者user9822311

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 10:14:44