Creating Contacts with SwiftUI

Creating Contacts with SwiftUI

Learn how to add contacts to the user’s device within a SwiftUI app.

The Contacts framework provides tools for reading, creating, and editing contacts from the device's address book.

Let’s see how to create a contact using CNMutableContact and save the data with a CNSaveRequest in a SwiftUI-based application.


Before moving on, there are two things to address first:

  1. To be able to work with the user’s contact book your app needs to ask for permission first. You do that by adding the Privacy - Contacts Usage Description key to the Info tab of your project settings.
  2. For your application to access contacts, it needs to be tested using the Simulator or running on a device. It won’t work on Xcode Preview.

The article Getting started with the Contacts framework gives you a good introduction on how to use the Contacts framework within a SwiftUI application.


To create a new contact, use the CNMutableContact type. It is a class responsible for representing a new contact that can then be saved in the system. Here is an example:

let contact = CNMutableContact()

contact.givenName = "John"
contact.familyName = "Doe"
contact.phoneNumbers = [
    CNLabeledValue(
        label: CNLabelPhoneNumberMobile,
        value: CNPhoneNumber(stringValue: "+39 333 222 4444")
    )
]

Check the documentation page of CNMutableContact to see all the different properties that can be set when creating a new contact programmatically.

To save the contact to the device's contact list, use CNSaveRequest, which represents a save operation to be executed by the contact store.

let saveRequest = CNSaveRequest()

saveRequest.add(contact, toContainerWithIdentifier: nil)
try contactStore.execute(saveRequest)
            
statusMessage = "Contact saved successfully."
name = ""
phone = ""

Then, when you enter the device's contacts, you will find the contact added. Also include a status message to let the user know the action was successful and reset the name and phone number fields.


Putting it all together in a SwiftUI view you can come up with something like the example below:

import SwiftUI
import Contacts

struct NewContactView: View {
    
    @State private var name: String = ""
    @State private var surname: String = ""
    
    @State private var phoneNumber: String = ""
    
    @State private var statusMessage: String = ""
    
    var body: some View {
        NavigationStack {
            Form {
                Section("Indentification") {
                    TextField("Name", text: $name)
                    TextField("Surname", text: $surname)
                        
                }
                
                Section("Phone Info") {
                    TextField("Phone", text: $phoneNumber)
                        .keyboardType(.phonePad)
                }
                
                
                
                Section {
                    Button("Save Contact") {
                        Task {
                            await saveContact()
                        }
                    }
                    
                    if !statusMessage.isEmpty {
                        Text(statusMessage)
                            .foregroundColor(.gray)
                            .listRowSeparator(.hidden)
                    }
                }
                
                
            }
            .navigationTitle("New contact")
        }
    }
    
    
    private let contactStore = CNContactStore()
    
    private func requestAccess() async throws -> Bool {
        return try await contactStore.requestAccess(for: .contacts)
    }
    
    
    private func createContact() async -> CNMutableContact? {
        let newContact = CNMutableContact()
        
        newContact.givenName = name
        newContact.phoneNumbers = [
            CNLabeledValue(
                label: CNLabelPhoneNumberMobile,
                value: CNPhoneNumber(stringValue: phoneNumber)
            )
        ]
        
        return newContact
    }
    
    private func saveContact() async {
        do {
            let accessGranted = try await requestAccess()
            
            guard accessGranted else {
                statusMessage = "No permission to access contacts."
                return
            }
            
            if let newContact = await createContact() {
                let saveRequest = CNSaveRequest()
                saveRequest.add(newContact, toContainerWithIdentifier: nil)
                try contactStore.execute(saveRequest)
                
                statusMessage = "Contact saved successfully."
                name = ""
                phoneNumber = ""
            } else {
                statusMessage = "Could not create the new contact."
            }
        } catch {
            statusMessage = "Error saving contact: \(error.localizedDescription)"
        }
    }
}

You can also isolate the logic for creating and saving contacts in a separate service within your app.