Contacts for Swift: Complete Guide with Examples
1. What It Is and Why You Need It
The Contacts framework (Contacts.framework) is Apple's official API for working with the address book on iOS, macOS, watchOS, and tvOS. It replaces the legacy AddressBook framework and provides a modern, object-oriented interface for accessing user contacts. Its primary purpose is to unify reading, creating, updating, and deleting contacts, as well as managing contact groups.
Why do you need it? Any app that interacts with the device's contact list — messengers, CRM systems, dialer apps, social networks — should use Contacts. The framework lets you safely request user permissions, efficiently load large datasets (via batch fetching and predicates), and work with unified contacts (when a single person is merged from multiple sources like iCloud and Google).
Unlike older solutions, Contacts fully supports Swift (including async/await), uses CNContactStore as a single entry point, and provides immutable objects for reading and mutable ones for writing, making your code safer and more predictable.
2. Installation
The Contacts framework is part of Apple's system frameworks. You don't need to install it via package managers like CocoaPods, SPM, or Carthage — it's already available on the device. Simply import the module in your code.
Important: To work with contacts, you must add the NSContactsUsageDescription key to your project's Info.plist file. Without it, your app will crash when trying to access the contact store.
Command (not required, but for clarity):
Since this is a system framework, there are no pip, npm, or cargo commands. Just add the import to your Swift file:
import Contacts
3. Quick Start — Minimal Working Example
This example requests permission to access contacts and prints all contact names from the address book to the console.
import Contacts
import UIKit
// 1. Create a contact store instance
let store = CNContactStore()
// 2. Request permission
store.requestAccess(for: .contacts) { granted, error in
guard granted else {
print("Access denied: \(error?.localizedDescription ?? "Unknown error")")
return
}
// 3. Create a fetch request
let keysToFetch = [CNContactGivenNameKey, CNContactFamilyNameKey]
let request = CNContactFetchRequest(keysToFetch: keysToFetch)
// 4. Perform the fetch
do {
try store.enumerateContacts(with: request) { contact, stopPointer in
print("Found contact: \(contact.givenName) \(contact.familyName)")
}
} catch {
print("Error fetching contacts: \(error.localizedDescription)")
}
}
4. Core Methods / Functions / Classes
4.1. CNContactStore
Signature: class CNContactStore : NSObject
What it does: The central class for accessing the contact database. All operations — queries, saves, deletes — go through this class.
Example:
let store = CNContactStore()
// Use store for all operations
4.2. requestAccess(for:completionHandler:)
Signature: func requestAccess(for entityType: CNEntityType, completionHandler: @escaping (Bool, Error?) -> Void)
What it does: Requests the user's permission to access contacts. The completion handler returns true if granted, or an error if denied.
Example:
store.requestAccess(for: .contacts) { granted, error in
if granted {
print("Permission granted!")
} else {
print("Permission denied: \(error?.localizedDescription ?? "")")
}
}
4.3. enumerateContacts(with:usingBlock:)
Signature: func enumerateContacts(with fetchRequest: CNContactFetchRequest, usingBlock block: @escaping (CNContact, UnsafeMutablePointer
What it does: Iterates through all contacts matching the fetch request. Use this for batch processing without loading everything into memory at once.
Example:
let keys = [CNContactGivenNameKey, CNContactEmailAddressesKey]
let request = CNContactFetchRequest(keysToFetch: keys)
try store.enumerateContacts(with: request) { contact, _ in
print("\(contact.givenName) — \(contact.emailAddresses.first?.value ?? "no email")")
}
4.4. unifiedContact(withIdentifier:keysToFetch:)
Signature: func unifiedContact(withIdentifier identifier: String, keysToFetch keys: [CNKeyDescriptor]) throws -> CNContact
What it does: Returns a unified contact (merged from multiple sources) by its identifier. Use this when you have a contact ID and need the full, merged record.
Example:
let contactID = "AB1234-5678"
let keys = [CNContactGivenNameKey, CNContactPhoneNumbersKey]
if let contact = try? store.unifiedContact(withIdentifier: contactID, keysToFetch: keys) {
print("Contact: \(contact.givenName), phones: \(contact.phoneNumbers)")
}
4.5. execute(_:)
Signature: func execute(_ saveRequest: CNSaveRequest) throws
What it does: Executes a save request to add, update, or delete contacts and groups. This is the write counterpart to the read operations.
Example (adding a contact):
let newContact = CNMutableContact()
newContact.givenName = "John"
newContact.familyName = "Appleseed"
newContact.phoneNumbers = [CNLabeledValue(label: CNLabelPhoneNumberiPhone, value: CNPhoneNumber(stringValue: "+1-555-123-4567"))]
let saveRequest = CNSaveRequest()
saveRequest.add(newContact, toContainerWithIdentifier: nil)
try store.execute(saveRequest)
print("Contact saved!")
5. Common Use Cases
5.1. Reading All Contacts
Fetch all contacts with specific fields (e.g., name and phone number):
let keys = [CNContactGivenNameKey, CNContactFamilyNameKey, CNContactPhoneNumbersKey]
let request = CNContactFetchRequest(keysToFetch: keys)
try store.enumerateContacts(with: request) { contact, _ in
let name = "\(contact.givenName) \(contact.familyName)"
let phones = contact.phoneNumbers.map { $0.value.stringValue }
print("\(name): \(phones.joined(separator: ", "))")
}
5.2. Searching Contacts by Name
Use a predicate to filter contacts efficiently:
let predicate = CNContact.predicateForContacts(matchingName: "John")
let keys = [CNContactGivenNameKey, CNContactFamilyNameKey]
let contacts = try store.unifiedContacts(matching: predicate, keysToFetch: keys)
for contact in contacts {
print("Found: \(contact.givenName) \(contact.familyName)")
}
5.3. Updating an Existing Contact
// Fetch the contact first
let predicate = CNContact.predicateForContacts(matchingName: "John Appleseed")
let keys = [CNContactGivenNameKey, CNContactEmailAddressesKey]
let contacts = try store.unifiedContacts(matching: predicate, keysToFetch: keys)
if let contact = contacts.first?.mutableCopy() as? CNMutableContact {
contact.emailAddresses = [CNLabeledValue(label: CNLabelHome, value: "john@example.com")]
let saveRequest = CNSaveRequest()
saveRequest.update(contact)
try store.execute(saveRequest)
print("Contact updated!")
}
5.4. Deleting a Contact
let predicate = CNContact.predicateForContacts(matchingName: "John Appleseed")
let keys = [CNContactGivenNameKey]
let contacts = try store.unifiedContacts(matching: predicate, keysToFetch: keys)
if let contact = contacts.first?.mutableCopy() as? CNMutableContact {
let saveRequest = CNSaveRequest()
saveRequest.delete(contact)
try store.execute(saveRequest)
print("Contact deleted!")
}
6. Best Practices and Tips
- Always request permission first — call
requestAccess(for:)before any contact operations. Handle the denial gracefully. - Fetch only the keys you need — requesting unnecessary keys (like all available keys) slows down performance. Specify only the fields you actually use.
- Use predicates for filtering — instead of loading all contacts and filtering in memory, use
predicateForContacts(matchingName:)orpredicateForContacts(withIdentifiers:)for server-side filtering. - Batch saves for multiple changes — if you're adding or updating many contacts, group them into a single
CNSaveRequestfor better performance. - Handle errors properly — contact operations can fail due to permission issues, iCloud sync conflicts, or data corruption. Always wrap calls in
do-catchblocks. - Use async/await for modern Swift — the framework supports Swift concurrency. Use
await store.requestAccess(for: .contacts)for cleaner code.
7. Conclusion
The Contacts framework is a powerful, modern tool for integrating address book functionality into your Apple platform apps. With its clean API, support for unified contacts, and seamless Swift integration, it's the go-to solution for any app that needs to read, write, or manage contacts. Start by importing the framework, requesting permission, and using CNContactStore as your central hub — and you'll be building contact-aware features in no time.
Also in library
Working with Markdown in TypeScript: Libraries, Parsers, and Tools
Geolocation and Maps in TypeScript: Libraries, API, and Code Examples
PDF Generation in JavaScript (Node.js): Libraries and Tools
Sending Email in Java: JavaMail and Simple Java Mail Libraries