View, create, and edit contacts in the system Contacts UI
One.iOS.CNContactViewController presents ContactsUI’s native contact
card or editor inside a navigation controller. Reach for it when the
user should see and edit a contact in Apple’s own UI; for silent reads
and writes from your own screens, One.Contacts plain records are the
better fit.
This is iOS only. Constructing any of the three classes off iOS throws, since ContactsUI has no counterpart elsewhere.
import { One } from 'one'
const draft = new One.iOS.CNMutableContact()draft.givenName = 'Ada'draft.familyName = 'Lovelace'const controller = new One.iOS.CNContactViewController({ forNewContact: draft })controller.contactStore = new One.iOS.CNContactStore()controller.delegate = { contactViewController: async (sender, { didCompleteWith }) => { // undefined means Cancel; otherwise this is the native saved contact. await sender.dismiss() },}await controller.present()The three initializer forms take { forContact }, { forUnknownContact }, or { forNewContact }. Pass undefined for an empty new contact.
Set ios.infoPlist.NSContactsUsageDescription before prebuild; the
contacts.usage string stamps the same key.
The delegate owns dismissal, as in ContactsUI. present resolves after
presentation, before Save or Cancel. Completion adapts the SDK’s labeled
contactViewController(_:didCompleteWith:) to a JavaScript result
object: a nil contact becomes undefined, no saved contact is fabricated
for cancellation, and completion delivers at most once per presentation.
Interactive sheet dismissal ends presentation ownership without
synthesizing a Save or Cancel result.
To display an existing contact, fetch the SDK’s required keys first. The shared Contacts service can request permission or find identifiers, but its plain records are not native contacts:
import { One } from 'one'
async function viewContact(identifier: string) { await One.Contacts.requestPermission() const store = new One.iOS.CNContactStore() const contact = await store.unifiedContact({ withIdentifier: identifier, keysToFetch: [One.iOS.CNContactViewController.descriptorForRequiredKeys()], }) const viewer = new One.iOS.CNContactViewController({ forContact: contact }) viewer.contactStore = store viewer.allowsEditing = false await viewer.present()}unifiedContact adapts Swift’s throwing fetch to a promise and labeled
arguments to an options object. keysToFetch takes SDK string keys and
native key descriptors. The initializers reject incomplete fetches
before ContactsUI can raise a missing-property exception. Fetching uses
native authorization, including limited contacts access; request access
with One.Contacts.requestPermission.
Results retain native contacts. The bound subset covers the identifier,
name and organization fields, isKeyAvailable, and mutableCopy. A
mutable copy is independent and keeps the SDK contact identifier, with
SDK empty-string defaults. Reading or changing an unfetched field
throws; changing an immutable contact needs mutableCopy first. Phone
numbers, addresses, images, dates, and other properties stay editable in
the native UI but are not exposed by this subset.
The card keeps SDK defaults for its initializer mode. Configure it with
displayedPropertyKeys, alternateName, message, allowsEditing,
allowsActions, shouldShowLinkedContacts, and highlightProperty.
Assigning contactStore = undefined clears the SDK store. Parent groups
and containers are not bound. The synchronous
shouldPerformDefaultActionFor delegate method is not bound because
JavaScript callbacks run on a different thread from UIKit; its native
default stays in effect, and allowsActions can disable actions.
| Contact values | Type | Description |
|---|---|---|
identifierstringNative contact identifier. | string | Native contact identifier. |
namePrefixstringName prefix. | string | Name prefix. |
givenNamestringGiven name. | string | Given name. |
middleNamestringMiddle name. | string | Middle name. |
familyNamestringFamily name. | string | Family name. |
nameSuffixstringName suffix. | string | Name suffix. |
nicknamestringNickname. | string | Nickname. |
organizationNamestringOrganization name. | string | Organization name. |
jobTitlestringJob title. | string | Job title. |
isKeyAvailable(key: string) => booleanWhether the SDK fetched the property. | (key: string) => boolean | Whether the SDK fetched the property. |
mutableCopy() => CNMutableContactValueIndependent native mutable copy. | () => CNMutableContactValue | Independent native mutable copy. |
| Mutable contact fields | Type | Description |
|---|---|---|
namePrefixstringMutable name prefix. | string | Mutable name prefix. |
givenNamestringMutable given name. | string | Mutable given name. |
middleNamestringMutable middle name. | string | Mutable middle name. |
familyNamestringMutable family name. | string | Mutable family name. |
nameSuffixstringMutable name suffix. | string | Mutable name suffix. |
nicknamestringMutable nickname. | string | Mutable nickname. |
organizationNamestringMutable organization name. | string | Mutable organization name. |
jobTitlestringMutable job title. | string | Mutable job title. |
| Store | Type | Description |
|---|---|---|
unifiedContact(options: CNContactFetchOptions) => Promise<CNContactValue>Fetches a native contact with the requested keys. | (options: CNContactFetchOptions) => Promise<CNContactValue> | Fetches a native contact with the requested keys. |
| Fetch arguments | Type | Description |
|---|---|---|
withIdentifierstringStored contact identifier. | string | Stored contact identifier. |
keysToFetch(string | CNKeyDescriptor)[]SDK string keys and required-key descriptors. | (string | CNKeyDescriptor)[] | SDK string keys and required-key descriptors. |
| Controller | Type | Description |
|---|---|---|
contactCNContactValueNative displayed contact, readonly. | CNContactValue | Native displayed contact, readonly. |
delegateCNContactViewControllerDelegate | nullReceives saved contact or cancellation. | CNContactViewControllerDelegate | null | Receives saved contact or cancellation. |
contactStoreCNContactStoreValue | undefinedSDK store used for edits and additions. | CNContactStoreValue | undefined | SDK store used for edits and additions. |
displayedPropertyKeysstring[] | undefinedVisible SDK property keys; undefined shows all. | string[] | undefined | Visible SDK property keys; undefined shows all. |
alternateNamestring | undefinedDisplay name when no contact name exists. | string | undefined | Display name when no contact name exists. |
messagestring | undefinedMessage displayed under the name. | string | undefined | Message displayed under the name. |
allowsEditingbooleanSDK editing permission for the card. | boolean | SDK editing permission for the card. |
allowsActionsbooleanWhether native contact actions appear. | boolean | Whether native contact actions appear. |
shouldShowLinkedContactsbooleanWhether linked contacts appear. | boolean | Whether linked contacts appear. |
highlightProperty(withKey: string, identifier?: string) => voidHighlights a property or labeled-value identifier. | (withKey: string, identifier?: string) => void | Highlights a property or labeled-value identifier. |
present(animated?: boolean) => Promise<void>Presents native navigation and contact UI; animated defaults to true. | (animated?: boolean) => Promise<void> | Presents native navigation and contact UI; animated defaults to true. |
dismiss(animated?: boolean) => Promise<void>Dismisses the native UI; animated defaults to true. | (animated?: boolean) => Promise<void> | Dismisses the native UI; animated defaults to true. |
| iOS | Android | Web | |
|---|---|---|---|
| UI | Contacts card | Throws | Throws |
| Store | Native fetch | Throws | Throws |
| Editing | Native editor | None | None |
isKeyAvailable before reading a
field the fetch may have skipped.mutableCopy first, then edit.import { One } from 'one'
async function saveContact(givenName: string, familyName: string) { const draft = new One.iOS.CNMutableContact() draft.givenName = givenName draft.familyName = familyName const controller = new One.iOS.CNContactViewController({ forNewContact: draft }) controller.contactStore = new One.iOS.CNContactStore() const saved = await new Promise<string | undefined>((resolve) => { controller.delegate = { contactViewController: async (sender, { didCompleteWith }) => { await sender.dismiss() resolve(didCompleteWith?.identifier) }, } void controller.present() }) return saved}The promise bridges the delegate callback into async flow: an identifier on Save, undefined on Cancel or sheet dismissal. The store assignment lets the editor persist the new contact.
import { One } from 'one'
async function showCard(identifier: string) { await One.Contacts.requestPermission() const store = new One.iOS.CNContactStore() const contact = await store.unifiedContact({ withIdentifier: identifier, keysToFetch: [One.iOS.CNContactViewController.descriptorForRequiredKeys()], }) const card = new One.iOS.CNContactViewController({ forContact: contact }) card.contactStore = store card.allowsEditing = false card.allowsActions = false card.delegate = { contactViewController: async (sender) => { await sender.dismiss() }, } await card.present()}Disabling editing and actions turns the card into a pure viewer. The delegate still dismisses, since Done still needs its dismissal.
Edit this page on GitHub.