Streaming is available in most browsers,
and in the Developer app.
-
Meet the Contact Access Button
Learn about the new Contacts authorization modes and how to improve Contacts access in your app. Discover how to integrate the Contact Access Button into your app to share additional contacts on demand and provide an easier path to Contacts authorization. We'll also cover Contacts security features and an alternative API to be used if the button isn't appropriate for your app.
Chapters
- 0:00 - Introduction
- 0:36 - Limited Access
- 2:56 - Contact Access Button
- 8:28 - Accessing contacts
- 13:34 - Which access method to use
Resources
-
Download
Hello! I'm Ada, and in this video I'm going to give you a quick introduction to the ContactAccessButton, and how you can use it in your app. First, I will give an overview of the Limited Access feature available in iOS 18. Then, I will tell you about the new ContactAccessButton API, and how you can use it to create a great Contacts experience, right in your app! Finally, I'll discuss other ways of accessing contacts, and how they work with limited access.
In iOS 18, Contacts authorization now has the option to only share a portion of the contacts database. This is called Limited access. The Contacts authorization prompt now comes in two stages. The first alert asks, whether to share contacts with the app or not. If Continue is tapped, a second prompt is shown, where two different options for sharing contacts are offered. Either a set of contacts can be selected for sharing, or the app can receive access to everything The prompt emphasizes that this is not a final choice, and that the set of contacts can be extended in the future.
Once the desired set of contacts has been selected, a confirmation screen appears where the selected contacts are shown. This new authorization flow brings significant improvement to contacts privacy, with transparency and control over what is shared. With this new feature, there are now 4 authorization levels. With full access, your app has access to read any contact, and it also has the ability to modify contacts, or create new ones.
With limited access, your app can read the contacts it was granted access to. Just like Full access, your app can modify or create contacts.
Your app always starts, in the Not Determined authorization status. If you attempt to access the contact store, the authorization prompt will be presented modally.
If your app is authorized, the access attempt will be successful. Finally, if your app has the Denied authorization level, it will not be able to read or write contact data.
To create great experiences when access is limited, iOS 18 includes two new APIs to grant your app access to additional contacts. The contact access picker makes it easy to change which contacts your app has access to, without leaving your app.
The contact access button is an powerful new way, to manage access to contacts, right in your app. Instead of a full-screen picker, this button fits into your existing UI, and can grant access to new contacts, with a single tap. Now that you understand the basics of accessing contacts, I'm excited to go over the best way for your app to handle limited access, the Contact Access Button! The ContactAccessButton represents a streamlined way for your app to receive access to additional contacts in your app.
When you add it to your app's contact search flow, the button shows search results for contacts that your app doesn't yet have access to. It is designed to be a seamless part of your app. When the button has a unique match, it only takes one tap for the button to grant your app access to the contact. It provides a full-featured contact picker experience in your app, without requiring full access.
This incremental approach to limited access means that your app will get access to exactly the contacts it needs to have, right when it needs them, and will result in people feeling more confident sharing contacts with your app.
The best time to ask for authorization is the moment when access is required by an interaction. If requested at the time the data is needed, it is obvious what functionality your app will provide, if access is granted.
Contact Access Button is designed around this principle. When you use it, you are orienting your app around a workflow that seeks access when there is a clear need for each contact to be shared.
Instead of asking people to guess which contacts your app will need, the button allows them to make decisions in a context where they know exactly which contacts they wish to share.
The Contact Access Button takes this idea a step further. It can be presented while your app's authorization level is still in the not determined state.
If the button is tapped before your app has authorization, it automatically shows a simplified prompt that requests limited access. Since this occurs immediately after tapping on a contact search result, it is easy to understand why your app wants access, and your app is much more likely to receive access.
Here is an example of how to use the the button in an app. It is a simple view for showing contact search results in my app.
This view has a binding, to the text that the was entered into my app's search text field.
And it is tracking my app’s authorization status.
First, I show results that my app is able to retrieve from its own data store. I'm using a simple ForEach to do that here.
After my app's own results, I conditionally show the contact access button, only if my app has the limited or notDetermined authorization status.
When I initialize the button, I pass in the text that was entered into the search field.
Finally, in the callback block, I'll receive an array of contact identifier strings. At this point, I can retrieve the contact details and dismiss my search UI.
The button is designed to be tailored to fit the appearance of your app, using standard SwiftUI modifiers.
The font modifier controls the appearance of the upper line of text and the trailing action label.
The foregroundStyle modifier changes the color of the primary text.
The action label uses your app's tint color.
Lastly, there are a couple of button-specific modifiers. When there is a single match, the button can show some of the matching contact's information, which can be specified with the contactAccessButtonCaption modifier.
The contact photo's size can also be customized, using the contactAccessButtonStyle modifier.
The contact button is a powerful and convenient way for your app to get additional access, with strong privacy protections. The button displays sensitive information before apps have access to it, so the contents of the button remain private, but visible in your app.
Furthermore, the button only responds to validated events.
Lastly, the button only grants access when tapped if the contents are clearly legible and unobstructed. This ensures that it is always clear what tapping the button will do.
The button must be legible and unobstructed. Otherwise, the button will not grant your app access to additional contacts.
First, an example of a button configured with good contrast between the foreground and background, with enough room to render, and the entire button is visible. This satisfies all of the requirements for button legibility. Below, an example of a button that is configured improperly. The contrast ratio between the text and the background is very low, with light gray text on a white background Part of the button has been clipped, obscuring the button.
This button will not grant access to contacts when tapped.
Always make sure that the Contact Access Button is legible, unobscured, and has enough room to render.
In addition to the button, Contacts framework has 3 other ways that you can access contact data I’ll go over how they work, and what limited access means when using them.
CNContactStore is the primary way for you to access contact data. It can fetch the contacts, and create new contact entries.
When your app attempts to access contact data, CNContactStore automatically presents the authorization prompt.
Check what authorization level your app has using CNContactStore, and use that to display appropriate UI.
Attempting to read or write contact data will succeed only when your app has authorization.
It also notifies when contact data changes, and can be used to determine what changed.
When access is limited, the contact store will provide contact entries that have been shared with your app. Determine whether full or partial access was granted by checking your authorization status for the new .limited enum case. The .limited status is provided to determine which UI to present. This is useful for selectively using the new Contacts APIs designed for .limited authorization, like the ContactAccessButton.
Finally, CNContactStore is an essential part of using the ContactAccessButton, which returns an array of contact identifiers. Use CNContactStore to retrieve the contact data using those identifiers. I’ll show an example of how that works. Here is a function that takes in an array of contact identifier strings, and returns the CNContacts that those identifiers represent. To avoid blocking the main actor, I perform the fetch in a Task that I await.
Within the task, I first create a CNContactFetchRequest where I list out the keys for the contact fields I want to fetch. In this example, I only fetch the fields required for showing contact names.
To fetch contacts by their identifiers, I use CNContact predicateForContacts(withIdentifiers:).
After that, I use CNContactStore's enumerateContacts(with:), and collect the results into an array. The next way to access contact data is with the CNContactPickerViewController. This view controller presents system UI for picking one or more contacts from the contact database. After contacts have been selected, your app receives a one-time snapshot of the selected contacts. This view controller works regardless of what authorization level your app holds, and it always shows the whole contact database.
User interaction gives implicit permission, and your app receives contact data when one or more contacts are selected in the picker.
CNContactPickerViewController is best used in cases where you only require contact information for a one-off task, such as picking an email address or phone number, as the contacts are only shared with the app momentarily.
iOS 18 has a new picker, called contactAccessPicker, that presents a modal sheet where your app's limited access contacts set can be changed. Use contactAccessPicker to access additional contacts for bulk, or non-immediate use cases. For example, social apps might want to have an affordance to share additional contacts for friend matching purposes.
contactAccessPicker returns identifiers for the newly permitted contacts.
This is different from CNContactPickerViewController. The contactAccessPicker is for managing which contacts an app is able to access, through CNContactStore. CNContactPickerViewController provides access to one-time snapshots of contact data.
Here's an example of how to use it. In this case, I have a simple Button that toggles a State bool. Then, I use the contactAccessPicker method to make my button present the picker when the bool is true.
Finally, in the callback block, I fetch the contacts my app just gained access to.
Just like ContactAccessButton, this callback block receives an array of contact identifier strings, so I fetch the contacts the exact same way. Note that the callback block only reports contacts that are newly accessible. All of the examples I have gone over are available in the sample app associated with this video, along with comments that explain how the APIs are being used.
There are four different ways to access contacts, each impacted differently by Authorization level, and here’s when to use them. CNContactPickerViewController works no matter which authorization level, and allows your app to get one-time access to contacts selected in the view controller, always. CNContactStore is the main gateway to contact data, and requires authorization. In the rare case when your app has Full authorization, it has the ability to read or write any contact data. When your app has limited authorization, it can only access contacts in the permitted set.
When your app has not determined authorization, CNContactStore shows an authorization prompt when queried.
If your app is denied authorization, it does not have access to contact data. ContactAccessButton is for accessing additional contacts, when your app has limited authorization. When tapped, retrieve the contacts using CNContactStore. The button can also be used before your app has authorization. ContactAccessButton in your UI is the best experience when your app has limited authorization.
contactAccessPicker is for managing your app's limited access, and should only be used when your app has limited authorization.
Now you know all of the Contacts authorization levels and access methods.
So, what's next? Run your app on iOS18 and test how it works with limited access. Use the ContactAccessButton for Contacts access management, right in the UI of your app, right when it needs them.
Decide which contacts access method is the best way, to have someone share their contact information in your app.
Thanks for watching!
-
-
5:15 - Using ContactAccessButton
// Using ContactAccessButton @Binding var searchText: String @State var authorizationStatus: CNAuthorizationStatus = .notDetermined var body: some View { List { ForEach(searchResults(for: searchText)) { person in ResultRow(person) } if authorizationStatus == .limited || authorizationStatus == .notDetermined { ContactAccessButton(queryString: searchText) { identifiers in let contacts = await fetchContacts(withIdentifiers: identifiers) dismissSearch(withResult: contacts) } } } }
-
6:10 - Appearance options
ContactAccessButton(queryString: searchText) .font(.system(weight: .bold)) .foregroundStyle(.gray) .tint(.green) .contactAccessButtonCaption(.phone) .contactAccessButtonStyle(ContactAccessButton.Style(imageWidth: 30))
-
10:11 - Fetching contacts with CNContactStore
// Fetching contacts with CNContactStore func fetchContacts(withIdentifiers identifiers: [String]) async -> [CNContact] { return await Task { let keys = [CNContactFormatter.descriptorForRequiredKeys(for: .fullName)] let fetchRequest = CNContactFetchRequest(keysToFetch: keys) fetchRequest.predicate = CNContact.predicateForContacts(withIdentifiers: identifiers) var contacts: [CNContact] = [] do { try CNContactStore().enumerateContacts(with: fetchRequest) { contact, _ in contacts.append(contact) } } catch { // ... } return contacts }.value }
-
12:47 - Using contactAccessPicker
// Using contactAccessPicker @State private var isPresented = false var body: some View { Button("Show picker") { isPresented.toggle() }.contactAccessPicker(isPresented: $isPresented) { identifiers in let contacts = await fetchContacts(withIdentifiers: identifiers) // use the new contacts! } }
-
-
Looking for something specific? Enter a topic above and jump straight to the good stuff.
An error occurred when submitting your query. Please check your Internet connection and try again.