The Question Mark - blog by Mark Volkmann

Contacts

Overview

iOS apps can access data managed by the Apple Contacts app.

ContactPicker

We can define a ContactPicker SwiftUI view that allows the user to select a contact using the same UI as the Apple Contacts app. This is done using the Contacts UI framework and its CNContactPickerViewController class.

import ContactsUI
import SwiftUI

struct ContactPicker: UIViewControllerRepresentable {
    class Coordinator: NSObject, CNContactPickerDelegate {
        private var parent: ContactPicker

        init(_ parent: ContactPicker) {
            self.parent = parent
        }

        func contactPicker(
            _ picker: CNContactPickerViewController,
            didSelect contact: CNContact
        ) {
            parent.contact = contact
        }
    }

    @Binding private var contact: CNContact?

    init(contact: Binding<CNContact?>) {
        _contact = contact
    }

    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }

    func makeUIViewController(context: Context)
        -> CNContactPickerViewController {
        let vc = CNContactPickerViewController()
        vc.delegate = context.coordinator
        return vc
    }

    // This method is required, but in this case it doesn't need to anything.
    func updateUIViewController(
        _ uiViewController: CNContactPickerViewController,
        context: Context
    ) {}
}

To use this in a SwiftUI view, do something like the following:

import Contacts
import SwiftUI

struct ContentView: View {
    @State private var cellNumber = ""
    @State private var contact: CNContact?
    @State private var firstName = ""
    @State private var lastName = ""

    var body: some View {
        VStack {
            Text(firstName)
            Text(lastName)
            Text(cellNumber)
            Button("Find in Contacts") {
                isFindingContact = true
            }
            .buttonStyle(.borderedProminent)
        }
        .onChange(of: contact) { _ in
            guard let contact else { return }

            firstName = contact.givenName
            lastName = contact.familyName

            // Find the first "Mobile" phone number.
            var phone = contact.phoneNumbers.first { phoneNumber in
                guard let label = phoneNumber.label else { return false }
                return label.contains("Mobile")
            }

            // If none was found, just use the first phone number.
            if phone == nil { phone = contact.phoneNumbers.first }

            // Get the phone number from this object.
            if let phone { cellNumber = phone.value.stringValue }
        }
        .sheet(isPresented: $isFindingContact) {
            ContactPicker(contact: $contact)
        }
    }
}