/// 通讯录管理器,负责权限检查、联系人读取、变化监听等功能
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()
}
网友评论