/// 通讯录管理器,负责权限检查、联系人读取、变化监听等功能
final class ContactManager {
// MARK: - 单例与初始化
static let shared = ContactManager()
private let contactStore = CNContactStore()
weak var delegate: ContactManagerDelegate?
// 用户默认存储,用于保存同步状态
private let userDefaults = UserDefaults.standard
private let lastSyncDateKey = "LastContactSyncDate"
private let syncAnchorKey = "LastContactSyncAnchor"
private init() {
setupNotifications()
}
deinit {
NotificationCenter.default.removeObserver(self)
}
let contactKeys: [CNKeyDescriptor] = [
CNContactIdentifierKey as CNKeyDescriptor,
CNContactGivenNameKey as CNKeyDescriptor,
CNContactFamilyNameKey as CNKeyDescriptor,
CNContactPhoneNumbersKey as CNKeyDescriptor
]
}
// MARK: - 权限管理
extension ContactManager {
/// 检查通讯录访问权限
/// - Parameter completion: 返回权限状态(是否有权限, 是否为limited权限)
func checkAuthorization(completion: @escaping (Bool, Bool) -> Void) {
let status = CNContactStore.authorizationStatus(for: .contacts)
switch status {
case .authorized:
completion(true, false)
case .limited:
completion(true, true)
case .notDetermined:
requestAuthorization(completion: completion)
case .denied, .restricted:
completion(false, false)
@unknown default:
completion(false, false)
}
}
/// 弹出提示用户手动去设置页开启完整权限
func showManualSettingAlert(on viewController: UIViewController) {
let alert = UIAlertController(
title: "通讯录访问权限",
message: "请前往设置 > 隐私 > 通讯录,允许本App访问所有联系人",
preferredStyle: .alert
)
alert.addAction(UIAlertAction(title: "取消", style: .cancel))
alert.addAction(UIAlertAction(title: "去设置", style: .default) { _ in
SystemFunc.goSetting()
})
viewController.present(alert, animated: true)
}
private func requestAuthorization(completion: @escaping (Bool, Bool) -> Void) {
contactStore.requestAccess(for: .contacts) { granted, _ in
DispatchQueue.main.async {
let newStatus = CNContactStore.authorizationStatus(for: .contacts)
if #available(iOS 18.0, *), newStatus == .limited {
completion(true, true)
} else {
completion(granted, false)
}
}
}
}
}
// MARK: - 联系人操作
extension ContactManager {
/// 获取联系人(分页加载)
/// - Parameters:
/// - pageNo: 批次号(0表示获取所有)
/// - pageSize: 单次获取的联系人信息条数
/// - completion: 返回读取到的联系人数组
func fetchContacts(pageNo: Int, pageSize: Int, completion: @escaping ([CNContact]) -> Void) {
// 使用全局队列异步加载联系人数据,确保不阻塞主线程
DispatchQueue.global(qos: .userInitiated).async { [weak self] in
guard let self = self else { return }
var contacts = [CNContact]() // 存储联系人数组
let request = CNContactFetchRequest(keysToFetch: contactKeys) // 创建联系人请求,指定需要获取的字段
request.sortOrder = .givenName // 按照名字排序
let startIndex = (pageNo - 1) * pageSize
let endIndex = pageNo * pageSize
var count = 0 // 计数器,用于记录已经遍历的联系人数量
do {
// 枚举联系人,获取指定字段的数据
try self.contactStore.enumerateContacts(with: request) { contact, stop in
// 如果 batch 不为 0,并且当前联系人在当前批次范围内,加入结果数组
if pageNo > 0 && count >= endIndex {
stop.pointee = true // 停止枚举
}
if pageNo == 0 || (count >= startIndex && count < endIndex) {
contacts.append(contact)
}
count += 1 // 每次遍历联系人时计数器加1
}
} catch {
print("⚠️ 读取联系人失败: \(error.localizedDescription)") // 捕获并打印错误
}
// 在主线程回调,更新UI
DispatchQueue.main.async {
completion(contacts)
}
}
}
/// 获取只包含电话号码的联系人
func fetchContactsWithPhoneNumbers(pageNo: Int, pageSize: Int, completion: @escaping ([CNContact]) -> Void) {
fetchContacts(pageNo: pageNo, pageSize: pageSize) { contacts in
let filteredContacts = contacts.filter { !$0.phoneNumbers.isEmpty }
completion(filteredContacts)
}
}
/// 按名字或手机号搜索联系人
func searchContacts(with query: String, completion: @escaping ([CNContact]) -> Void) {
let lowercasedQuery = query.lowercased()
fetchContacts(pageNo: 0, pageSize: .max) { contacts in
let filteredContacts = contacts.filter { contact in
let fullName = "\(contact.givenName) \(contact.familyName)".lowercased()
let hasMatchingName = fullName.contains(lowercasedQuery)
let hasMatchingPhone = contact.phoneNumbers.contains {
$0.value.stringValue.lowercased().contains(lowercasedQuery)
}
return hasMatchingName || hasMatchingPhone
}
completion(filteredContacts)
}
}
}
// MARK: - 通知处理
private extension ContactManager {
private func setupNotifications() {
NotificationCenter.default.addObserver(
self,
selector: #selector(contactStoreDidChange(_:)),
name: .CNContactStoreDidChange,
object: nil
)
}
@objc func contactStoreDidChange(_ noti: Notification) {
delegate?.contactManagerDidChangeContacts()
}
}
/// 通讯录变化代理
protocol ContactManagerDelegate: AnyObject {
/// 通讯录变化的回调方法
func contactManagerDidChangeContacts()
}
通讯录管理器
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。
推荐阅读更多精彩内容
- 在上篇随笔《C#开发微信门户及应用(17)-微信企业号的通讯录管理开发之部门管理》介绍了通讯录的部门的相关操作管理...
- 前面一篇随笔企业号的一些基础信息,以及介绍如何配置企业号的回调方式实现和企业号服务器进行沟通的桥梁。本篇主要还是继...
- 微信管理之【通讯录管理】 之前每次听关于微信管理的分享,我都很振奋。当我看到这些分享者整齐的微信通讯录时, 特别的...
- 简洁不看全文版本 1.设定备注规则 2.按照你的需求设定通讯录的分组标签 3.PC端微信批量分类联系人 这是我参照...
- 忽然有一天,我发现通讯录里有不认识的人。 天哪,你是谁!我什么时候加你的?我真的不记得了,可是又不好意思开口问;这...