contacts-framework
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseContacts Framework
Contacts 框架
Fetch, create, update, and pick contacts from the user's Contacts database using
, , and . Targets
Swift 6.2 / iOS 26+.
CNContactStoreCNSaveRequestCNContactPickerViewController使用、和从用户的联系人数据库中获取、创建、更新和选择联系人。目标环境为Swift 6.2 / iOS 26+。
CNContactStoreCNSaveRequestCNContactPickerViewControllerContents
目录
Setup
配置
Project Configuration
项目配置
- Add to Info.plist explaining why the app accesses contacts
NSContactsUsageDescription - No additional capability or entitlement is required for basic Contacts access
- For contact notes access, add the entitlement
com.apple.developer.contacts.notes
- 在Info.plist中添加,说明应用访问联系人的原因
NSContactsUsageDescription - 基础联系人访问无需额外的功能权限或授权
- 若需访问联系人备注,需添加权限
com.apple.developer.contacts.notes
Imports
导入框架
swift
import Contacts // CNContactStore, CNSaveRequest, CNContact
import ContactsUI // CNContactPickerViewControllerswift
import Contacts // CNContactStore, CNSaveRequest, CNContact
import ContactsUI // CNContactPickerViewControllerAuthorization
权限授权
Request access before fetching or saving contacts. The picker ()
does not require authorization -- the system grants access only to the contacts
the user selects.
CNContactPickerViewControllerswift
let store = CNContactStore()
func requestAccess() async throws -> Bool {
return try await store.requestAccess(for: .contacts)
}
// Check current status without prompting
func checkStatus() -> CNAuthorizationStatus {
CNContactStore.authorizationStatus(for: .contacts)
}在获取或保存联系人前需请求权限。选择器()无需授权——系统仅会授予对用户所选联系人的访问权限。
CNContactPickerViewControllerswift
let store = CNContactStore()
func requestAccess() async throws -> Bool {
return try await store.requestAccess(for: .contacts)
}
// 检查当前权限状态,不触发提示
func checkStatus() -> CNAuthorizationStatus {
CNContactStore.authorizationStatus(for: .contacts)
}Authorization States
授权状态
| Status | Meaning |
|---|---|
| User has not been prompted yet |
| Full read/write access granted |
| User denied access; direct to Settings |
| Parental controls or MDM restrict access |
| iOS 18+: user granted access to selected contacts only |
| 状态 | 含义 |
|---|---|
| 用户尚未收到权限提示 |
| 已授予完整的读写权限 |
| 用户拒绝了权限;引导至设置页面 |
| 家长控制或MDM限制了访问权限 |
| iOS 18+:用户仅授予对所选联系人的访问权限 |
Fetching Contacts
获取联系人
Use for predicate-based queries.
Use for batch enumeration of all contacts.
unifiedContacts(matching:keysToFetch:)enumerateContacts(with:usingBlock:)使用进行基于谓词的查询。使用批量枚举所有联系人。
unifiedContacts(matching:keysToFetch:)enumerateContacts(with:usingBlock:)Fetch by Name
按姓名获取
swift
func fetchContacts(named name: String) throws -> [CNContact] {
let predicate = CNContact.predicateForContacts(matchingName: name)
let keys: [CNKeyDescriptor] = [
CNContactGivenNameKey as CNKeyDescriptor,
CNContactFamilyNameKey as CNKeyDescriptor,
CNContactPhoneNumbersKey as CNKeyDescriptor
]
return try store.unifiedContacts(matching: predicate, keysToFetch: keys)
}swift
func fetchContacts(named name: String) throws -> [CNContact] {
let predicate = CNContact.predicateForContacts(matchingName: name)
let keys: [CNKeyDescriptor] = [
CNContactGivenNameKey as CNKeyDescriptor,
CNContactFamilyNameKey as CNKeyDescriptor,
CNContactPhoneNumbersKey as CNKeyDescriptor
]
return try store.unifiedContacts(matching: predicate, keysToFetch: keys)
}Fetch by Identifier
按标识符获取
swift
func fetchContact(identifier: String) throws -> CNContact {
let keys: [CNKeyDescriptor] = [
CNContactGivenNameKey as CNKeyDescriptor,
CNContactFamilyNameKey as CNKeyDescriptor,
CNContactEmailAddressesKey as CNKeyDescriptor
]
return try store.unifiedContact(withIdentifier: identifier, keysToFetch: keys)
}swift
func fetchContact(identifier: String) throws -> CNContact {
let keys: [CNKeyDescriptor] = [
CNContactGivenNameKey as CNKeyDescriptor,
CNContactFamilyNameKey as CNKeyDescriptor,
CNContactEmailAddressesKey as CNKeyDescriptor
]
return try store.unifiedContact(withIdentifier: identifier, keysToFetch: keys)
}Enumerate All Contacts
枚举所有联系人
Perform I/O-heavy enumeration off the main thread.
swift
func fetchAllContacts() throws -> [CNContact] {
let keys: [CNKeyDescriptor] = [
CNContactGivenNameKey as CNKeyDescriptor,
CNContactFamilyNameKey as CNKeyDescriptor
]
let request = CNContactFetchRequest(keysToFetch: keys)
request.sortOrder = .givenName
var contacts: [CNContact] = []
try store.enumerateContacts(with: request) { contact, _ in
contacts.append(contact)
}
return contacts
}在主线程外执行I/O密集型的枚举操作。
swift
func fetchAllContacts() throws -> [CNContact] {
let keys: [CNKeyDescriptor] = [
CNContactGivenNameKey as CNKeyDescriptor,
CNContactFamilyNameKey as CNKeyDescriptor
]
let request = CNContactFetchRequest(keysToFetch: keys)
request.sortOrder = .givenName
var contacts: [CNContact] = []
try store.enumerateContacts(with: request) { contact, _ in
contacts.append(contact)
}
return contacts
}Key Descriptors
键描述符
Only fetch the properties you need. Accessing an unfetched property throws
.
CNContactPropertyNotFetchedException仅获取你需要的属性。访问未获取的属性会抛出异常。
CNContactPropertyNotFetchedExceptionCommon Keys
常用键
| Key | Property |
|---|---|
| First name |
| Last name |
| Phone numbers array |
| Email addresses array |
| Mailing addresses array |
| Full-resolution contact photo |
| Thumbnail contact photo |
| Birthday date components |
| Company name |
| 键 | 属性 |
|---|---|
| 名字 |
| 姓氏 |
| 电话号码数组 |
| 邮箱地址数组 |
| 邮寄地址数组 |
| 全分辨率联系人照片 |
| 联系人照片缩略图 |
| 生日日期组件 |
| 公司名称 |
Composite Key Descriptors
复合键描述符
Use to fetch all keys needed
for formatting a contact's name.
CNContactFormatter.descriptorForRequiredKeys(for:)swift
let nameKeys = CNContactFormatter.descriptorForRequiredKeys(for: .fullName)
let keys: [CNKeyDescriptor] = [nameKeys, CNContactPhoneNumbersKey as CNKeyDescriptor]使用获取格式化联系人姓名所需的所有键。
CNContactFormatter.descriptorForRequiredKeys(for:)swift
let nameKeys = CNContactFormatter.descriptorForRequiredKeys(for: .fullName)
let keys: [CNKeyDescriptor] = [nameKeys, CNContactPhoneNumbersKey as CNKeyDescriptor]Creating and Updating Contacts
创建和更新联系人
Use to build new contacts and to persist changes.
CNMutableContactCNSaveRequest使用创建新联系人,使用持久化更改。
CNMutableContactCNSaveRequestCreating a New Contact
创建新联系人
swift
func createContact(givenName: String, familyName: String, phone: String) throws {
let contact = CNMutableContact()
contact.givenName = givenName
contact.familyName = familyName
contact.phoneNumbers = [
CNLabeledValue(
label: CNLabelPhoneNumberMobile,
value: CNPhoneNumber(stringValue: phone)
)
]
let saveRequest = CNSaveRequest()
saveRequest.add(contact, toContainerWithIdentifier: nil) // nil = default container
try store.execute(saveRequest)
}swift
func createContact(givenName: String, familyName: String, phone: String) throws {
let contact = CNMutableContact()
contact.givenName = givenName
contact.familyName = familyName
contact.phoneNumbers = [
CNLabeledValue(
label: CNLabelPhoneNumberMobile,
value: CNPhoneNumber(stringValue: phone)
)
]
let saveRequest = CNSaveRequest()
saveRequest.add(contact, toContainerWithIdentifier: nil) // nil = 默认容器
try store.execute(saveRequest)
}Updating an Existing Contact
更新现有联系人
You must fetch the contact with the properties you intend to modify, create a
mutable copy, change the properties, then save.
swift
func updateContactEmail(identifier: String, email: String) throws {
let keys: [CNKeyDescriptor] = [
CNContactEmailAddressesKey as CNKeyDescriptor
]
let contact = try store.unifiedContact(withIdentifier: identifier, keysToFetch: keys)
guard let mutable = contact.mutableCopy() as? CNMutableContact else { return }
mutable.emailAddresses.append(
CNLabeledValue(label: CNLabelWork, value: email as NSString)
)
let saveRequest = CNSaveRequest()
saveRequest.update(mutable)
try store.execute(saveRequest)
}你必须先获取包含要修改属性的联系人,创建可变副本,修改属性后再保存。
swift
func updateContactEmail(identifier: String, email: String) throws {
let keys: [CNKeyDescriptor] = [
CNContactEmailAddressesKey as CNKeyDescriptor
]
let contact = try store.unifiedContact(withIdentifier: identifier, keysToFetch: keys)
guard let mutable = contact.mutableCopy() as? CNMutableContact else { return }
mutable.emailAddresses.append(
CNLabeledValue(label: CNLabelWork, value: email as NSString)
)
let saveRequest = CNSaveRequest()
saveRequest.update(mutable)
try store.execute(saveRequest)
}Deleting a Contact
删除联系人
swift
func deleteContact(identifier: String) throws {
let keys: [CNKeyDescriptor] = [CNContactIdentifierKey as CNKeyDescriptor]
let contact = try store.unifiedContact(withIdentifier: identifier, keysToFetch: keys)
guard let mutable = contact.mutableCopy() as? CNMutableContact else { return }
let saveRequest = CNSaveRequest()
saveRequest.delete(mutable)
try store.execute(saveRequest)
}swift
func deleteContact(identifier: String) throws {
let keys: [CNKeyDescriptor] = [CNContactIdentifierKey as CNKeyDescriptor]
let contact = try store.unifiedContact(withIdentifier: identifier, keysToFetch: keys)
guard let mutable = contact.mutableCopy() as? CNMutableContact else { return }
let saveRequest = CNSaveRequest()
saveRequest.delete(mutable)
try store.execute(saveRequest)
}Contact Picker
联系人选择器
CNContactPickerViewControllerCNContactPickerViewControllerSwiftUI Wrapper
SwiftUI 封装
swift
import SwiftUI
import ContactsUI
struct ContactPicker: UIViewControllerRepresentable {
@Binding var selectedContact: CNContact?
func makeUIViewController(context: Context) -> CNContactPickerViewController {
let picker = CNContactPickerViewController()
picker.delegate = context.coordinator
return picker
}
func updateUIViewController(_ uiViewController: CNContactPickerViewController, context: Context) {}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
final class Coordinator: NSObject, CNContactPickerDelegate {
let parent: ContactPicker
init(_ parent: ContactPicker) {
self.parent = parent
}
func contactPicker(_ picker: CNContactPickerViewController, didSelect contact: CNContact) {
parent.selectedContact = contact
}
func contactPickerDidCancel(_ picker: CNContactPickerViewController) {
parent.selectedContact = nil
}
}
}swift
import SwiftUI
import ContactsUI
struct ContactPicker: UIViewControllerRepresentable {
@Binding var selectedContact: CNContact?
func makeUIViewController(context: Context) -> CNContactPickerViewController {
let picker = CNContactPickerViewController()
picker.delegate = context.coordinator
return picker
}
func updateUIViewController(_ uiViewController: CNContactPickerViewController, context: Context) {}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
final class Coordinator: NSObject, CNContactPickerDelegate {
let parent: ContactPicker
init(_ parent: ContactPicker) {
self.parent = parent
}
func contactPicker(_ picker: CNContactPickerViewController, didSelect contact: CNContact) {
parent.selectedContact = contact
}
func contactPickerDidCancel(_ picker: CNContactPickerViewController) {
parent.selectedContact = nil
}
}
}Using the Picker
使用选择器
swift
struct ContactSelectionView: View {
@State private var selectedContact: CNContact?
@State private var showPicker = false
var body: some View {
VStack {
if let contact = selectedContact {
Text("\(contact.givenName) \(contact.familyName)")
}
Button("Select Contact") {
showPicker = true
}
}
.sheet(isPresented: $showPicker) {
ContactPicker(selectedContact: $selectedContact)
}
}
}swift
struct ContactSelectionView: View {
@State private var selectedContact: CNContact?
@State private var showPicker = false
var body: some View {
VStack {
if let contact = selectedContact {
Text("\(contact.givenName) \(contact.familyName)")
}
Button("选择联系人") {
showPicker = true
}
}
.sheet(isPresented: $showPicker) {
ContactPicker(selectedContact: $selectedContact)
}
}
}Filtering the Picker
过滤选择器
Use predicates to control which contacts appear and what the user can select.
swift
let picker = CNContactPickerViewController()
// Only show contacts that have an email address
picker.predicateForEnablingContact = NSPredicate(format: "emailAddresses.@count > 0")
// Selecting a contact returns it directly (no detail card)
picker.predicateForSelectionOfContact = NSPredicate(value: true)使用谓词控制显示哪些联系人以及用户可以选择的内容。
swift
let picker = CNContactPickerViewController()
// 仅显示有邮箱地址的联系人
picker.predicateForEnablingContact = NSPredicate(format: "emailAddresses.@count > 0")
// 选择联系人后直接返回(不显示详情卡片)
picker.predicateForSelectionOfContact = NSPredicate(value: true)Observing Changes
监听变更
Listen for external contact database changes to refresh cached data.
swift
func observeContactChanges() {
NotificationCenter.default.addObserver(
forName: .CNContactStoreDidChange,
object: nil,
queue: .main
) { _ in
// Refetch contacts -- cached CNContact objects are stale
refreshContacts()
}
}监听外部联系人数据库的变更以刷新缓存数据。
swift
func observeContactChanges() {
NotificationCenter.default.addObserver(
forName: .CNContactStoreDidChange,
object: nil,
queue: .main
) { _ in
// 重新获取联系人——缓存的CNContact对象已过期
refreshContacts()
}
}Common Mistakes
常见错误
DON'T: Fetch all keys when you only need a name
不要:仅需要姓名时获取所有键
Over-fetching wastes memory and slows queries, especially for contacts with
large photos.
swift
// WRONG: Fetches everything including full-resolution photos
let keys: [CNKeyDescriptor] = [CNContactCompleteNameKey as CNKeyDescriptor,
CNContactImageDataKey as CNKeyDescriptor,
CNContactPhoneNumbersKey as CNKeyDescriptor,
CNContactEmailAddressesKey as CNKeyDescriptor,
CNContactPostalAddressesKey as CNKeyDescriptor,
CNContactBirthdayKey as CNKeyDescriptor]
// CORRECT: Fetch only what you display
let keys: [CNKeyDescriptor] = [
CNContactGivenNameKey as CNKeyDescriptor,
CNContactFamilyNameKey as CNKeyDescriptor
]过度获取会浪费内存并减慢查询速度,尤其是对于包含大尺寸照片的联系人。
swift
// 错误:获取所有内容,包括全分辨率照片
let keys: [CNKeyDescriptor] = [CNContactCompleteNameKey as CNKeyDescriptor,
CNContactImageDataKey as CNKeyDescriptor,
CNContactPhoneNumbersKey as CNKeyDescriptor,
CNContactEmailAddressesKey as CNKeyDescriptor,
CNContactPostalAddressesKey as CNKeyDescriptor,
CNContactBirthdayKey as CNKeyDescriptor]
// 正确:仅获取你需要展示的内容
let keys: [CNKeyDescriptor] = [
CNContactGivenNameKey as CNKeyDescriptor,
CNContactFamilyNameKey as CNKeyDescriptor
]DON'T: Access unfetched properties
不要:访问未获取的属性
Accessing a property that was not in throws
at runtime.
keysToFetchCNContactPropertyNotFetchedExceptionswift
// WRONG: Only fetched name keys, now accessing phone
let keys: [CNKeyDescriptor] = [CNContactGivenNameKey as CNKeyDescriptor]
let contact = try store.unifiedContact(withIdentifier: id, keysToFetch: keys)
let phone = contact.phoneNumbers.first // CRASH
// CORRECT: Include the key you need
let keys: [CNKeyDescriptor] = [
CNContactGivenNameKey as CNKeyDescriptor,
CNContactPhoneNumbersKey as CNKeyDescriptor
]访问未包含在中的属性会在运行时抛出异常。
keysToFetchCNContactPropertyNotFetchedExceptionswift
// 错误:仅获取了姓名键,现在访问电话
let keys: [CNKeyDescriptor] = [CNContactGivenNameKey as CNKeyDescriptor]
let contact = try store.unifiedContact(withIdentifier: id, keysToFetch: keys)
let phone = contact.phoneNumbers.first // 崩溃
// 正确:包含你需要的键
let keys: [CNKeyDescriptor] = [
CNContactGivenNameKey as CNKeyDescriptor,
CNContactPhoneNumbersKey as CNKeyDescriptor
]DON'T: Mutate a CNContact directly
不要:直接修改CNContact
CNContactmutableCopy()CNMutableContactswift
// WRONG: CNContact has no setter
let contact = try store.unifiedContact(withIdentifier: id, keysToFetch: keys)
contact.givenName = "New Name" // Compile error
// CORRECT: Create mutable copy
guard let mutable = contact.mutableCopy() as? CNMutableContact else { return }
mutable.givenName = "New Name"CNContactmutableCopy()CNMutableContactswift
// 错误:CNContact没有设置器
let contact = try store.unifiedContact(withIdentifier: id, keysToFetch: keys)
contact.givenName = "新名称" // 编译错误
// 正确:创建可变副本
guard let mutable = contact.mutableCopy() as? CNMutableContact else { return }
mutable.givenName = "新名称"DON'T: Skip authorization and assume access
不要:跳过授权直接访问
Without calling , fetch methods return empty results or throw.
requestAccess(for:)swift
// WRONG: Jump straight to fetch
let contacts = try store.unifiedContacts(matching: predicate, keysToFetch: keys)
// CORRECT: Check or request access first
let granted = try await store.requestAccess(for: .contacts)
guard granted else { return }
let contacts = try store.unifiedContacts(matching: predicate, keysToFetch: keys)如果未调用,获取方法会返回空结果或抛出异常。
requestAccess(for:)swift
// 错误:直接跳转到获取操作
let contacts = try store.unifiedContacts(matching: predicate, keysToFetch: keys)
// 正确:先检查或请求权限
let granted = try await store.requestAccess(for: .contacts)
guard granted else { return }
let contacts = try store.unifiedContacts(matching: predicate, keysToFetch: keys)DON'T: Run heavy fetches on the main thread
不要:在主线程执行大量获取操作
enumerateContactsswift
// WRONG: Main thread enumeration
func loadContacts() {
try store.enumerateContacts(with: request) { contact, _ in ... }
}
// CORRECT: Run on a background thread
func loadContacts() async throws -> [CNContact] {
try await Task.detached {
var results: [CNContact] = []
try store.enumerateContacts(with: request) { contact, _ in
results.append(contact)
}
return results
}.value
}enumerateContactsswift
// 错误:在主线程枚举
func loadContacts() {
try store.enumerateContacts(with: request) { contact, _ in ... }
}
// 正确:在后台线程运行
func loadContacts() async throws -> [CNContact] {
try await Task.detached {
var results: [CNContact] = []
try store.enumerateContacts(with: request) { contact, _ in
results.append(contact)
}
return results
}.value
}Review Checklist
检查清单
- added to Info.plist
NSContactsUsageDescription - called before fetch or save operations
requestAccess(for: .contacts) - Authorization denial handled gracefully (guide user to Settings)
- Only needed keys included in fetch requests
CNKeyDescriptor - used when formatting names
CNContactFormatter.descriptorForRequiredKeys(for:) - Mutable copy created via before modifying contacts
mutableCopy() - used for all create/update/delete operations
CNSaveRequest - Heavy fetches () run off the main thread
enumerateContacts - observed to refresh cached contacts
CNContactStoreDidChange - used when full Contacts access is unnecessary
CNContactPickerViewController - Picker predicates set before presenting the picker view controller
- Single instance reused across the app
CNContactStore
- 已在Info.plist中添加
NSContactsUsageDescription - 在执行获取或保存操作前调用了
requestAccess(for: .contacts) - 已优雅处理授权拒绝(引导用户至设置页面)
- 获取请求中仅包含所需的键
CNKeyDescriptor - 格式化姓名时使用了
CNContactFormatter.descriptorForRequiredKeys(for:) - 修改联系人前通过创建了可变副本
mutableCopy() - 所有创建/更新/删除操作都使用了
CNSaveRequest - 大量获取操作()在主线程外执行
enumerateContacts - 监听了以刷新缓存的联系人
CNContactStoreDidChange - 无需完整联系人访问时使用了
CNContactPickerViewController - 展示选择器视图控制器前设置了选择器谓词
- 应用中复用了单个实例
CNContactStore
References
参考资料
- Extended patterns (multi-select picker, vCard export, search optimization):
references/contacts-patterns.md - Contacts framework
- CNContactStore
- CNContactFetchRequest
- CNSaveRequest
- CNMutableContact
- CNContactPickerViewController
- CNContactPickerDelegate
- Accessing the contact store
- NSContactsUsageDescription
- 扩展模式(多选选择器、vCard导出、搜索优化):
references/contacts-patterns.md - Contacts 框架
- CNContactStore
- CNContactFetchRequest
- CNSaveRequest
- CNMutableContact
- CNContactPickerViewController
- CNContactPickerDelegate
- 访问联系人存储
- NSContactsUsageDescription