移动/游戏
在 iOS 应用中读取、创建、更新和选择联系人时使用…
在 Swift 6.3 / iOS 26+ 应用中通过 CNContactStore、CNSaveRequest 和 CNContactPickerViewController 获取、创建、更新或选择联系人。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 38/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 55/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 59/100 ×0.15
适用场景
在 iOS 应用中读取、创建、更新和选择特定
解决思路
- 在 Info.plist 中添加 NSContactsUsageDescription 说明访问联系人的原因;缺少该键时使用联系人数据 API 会崩溃。
- 普通联系人访问无需额外 capability 或 entitlement;仅在读写 CNContactNoteKey / CNContact.note 时添加 com.apple.developer.contacts.notes,该 entitlement 需 Apple 审核才能公开分发。
- 使用 @preconcurrency import Contacts 导入 CNContactStore、CNSaveRequest、CNContact,使用 import ContactsUI 导入 CNContactPickerViewController。
- 在获取或保存联系人前调用 store.requestAccess(for: .contacts) 请求授权;用 CNContactStore.authorizationStatus(for: .contacts) 检查当前状态而不弹窗。
- 将 .authorized 和 .limited 都视为可用的联系人 API 状态;在 .limited 下,获取、编辑、删除仅作用于用户授权或应用创建的联系人,可用 ContactAccessButton 或 contactAccessPicker(isPresented:completionHandler:) 让用户添加联系人到受限访问集合。
- 用 unifiedContacts(matching:keysToFetch:) 做基于谓词的查询,用 enumerateContacts(with:usingBlock:) 批量枚举所有联系人;对大型缓存通讯录,先获取标识符,再按标识符分批获取详细联系人。
- 按姓名获取时使用 CNContact.predicateForContacts(matchingName:) 并传入所需 keysToFetch。
- 按标识符获取时使用 store.unifiedContact(withIdentifier:keysToFetch:)。
- 枚举所有联系人时创建 CNContactFetchRequest(keysToFetch:),设置 request.sortOrder = .givenName,并在主线程之外执行 I/O 密集的枚举。
- 只获取需要的属性,避免访问未获取的属性导致 CNContactPropertyNotFetchedException;常用键包括 CNContactGivenNameKey、CNContactFamilyNameKey、CNContactPhoneNumbersKey、CNContactEmailAddressesKey、CNContactPostalAddressesKey、CNContactImageDataKey、CNContactThumbnailImageDataKey、CNContactBirthdayKey、CNContactOrganizationNameKey。
- 格式化姓名时使用 CNContactFormatter.descriptorForRequiredKeys(for: .fullName) 获取所需全部键。
- 创建新联系人时用 CNMutableContact 构建,设置属性后用 CNSaveRequest 的 add(contact, toContainerWithIdentifier: nil) 添加到默认容器,再执行 store.execute(saveRequest)。
- 更新联系人时先用包含待修改属性的 keysToFetch 获取联系人,调用 mutableCopy() 得到 CNMutableContact,修改属性后调用 saveRequest.update(mutable) 并执行 store.execute(saveRequest)。
- 删除联系人时先获取联系人,调用 mutableCopy() 得到 CNMutableContact,调用 saveRequest.delete(mutable) 并执行 store.execute(saveRequest)。
- 以 store.execute(saveRequest) 未抛错作为保存成功检查点,成功后才更新应用侧缓存或成功 UI;若抛错则展示或传播错误,保留未保存意图,修正授权、只读容器或无效输入等已知原因后再构建新请求;串行化重叠保存,不要在 execute(_:) 使用请求时访问该请求,重试前在权限允许时重新获取可能过期的联系人,不要盲目重复相同的破坏性请求。
- 用 CNContactPickerViewController 让用户在不授予完整联系人访问权限的情况下选择联系人,应用只收到所选联系人数据。
- 在 SwiftUI 中用 UIViewControllerRepresentable 包装 CNContactPickerViewController,设置 delegate,并在 contactPicker(_:didSelect:) 中回传所选联系人、在 contactPickerDidCancel(_:) 中清空选择。
- 用 sheet(isPresented:) 呈现包装后的选择器,并用 @State 保存 selectedContact 和 showPicker。
- 用 predicateForEnablingContact 控制哪些联系人可选,用 predicateForSelectionOfContact 控制选择后是否直接返回联系人(不显示详情卡),并在呈现选择器前设置好谓词。
- 监听 NotificationCenter 的 .CNContactStoreDidChange 通知,在主队列收到后重新获取联系人,因为缓存的 CNContact 对象已过期。
- 避免过度获取键:只获取 UI 实际显示的键,不要为只显示姓名而获取全分辨率照片、电话、邮箱、地址、生日等。
- 不要直接修改 CNContact(不可变),必须先 mutableCopy() 得到 CNMutableContact 再修改。
- 不要让获取或保存调用成为用户首次看到授权提示的地方:状态为 .notDetermined 时先请求授权,被拒绝时优雅引导用户到设置。
- 不要在主线程序列化执行 enumerateContacts 等 I/O 操作;严格并发检查报 CNContact 跨任务或 actor 边界错误时,在该文件使用 @preconcurrency import Contacts,或在返回前把联系人映射为 Sendable 视图模型。
- 在整个应用中复用单个 CNContactStore 实例。
适用边界 · 注意事项
- 不适用于非 Apple 平台或非 Swift/iOS 项目。
- 不需要访问系统联系人数据库、仅需用户手动输入联系人信息时不必使用本框架。
- 仅需选择联系人而不需要完整访问权限时,应使用 CNContactPickerViewController 而非请求完整授权。