如何匹配手机通讯录与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
相关产品推荐
相关产品推荐

