How do the Contacts, EventKit and Photos frameworks gate access, and how should an app handle limited or write-only access?

17.3.1 · L3 · Interview

Predict first

What does the contact formatter produce, and what are the EventKit and Photos status values?

import Contacts
import EventKit
import Photos

let k = CNMutableContact()
k.givenName = "Asha"; k.familyName = "Rao"
print(CNContactFormatter.string(from: k, style: .fullName)!)
print(EKAuthorizationStatus.notDetermined.rawValue, EKAuthorizationStatus.fullAccess.rawValue, EKAuthorizationStatus.writeOnly.rawValue)
print(PHAuthorizationStatus.limited.rawValue, PHAuthorizationStatus.authorized.rawValue, PHAuthorizationStatus.notDetermined.rawValue)
  • Asha Rao, then 0 3 4, then 4 3 0
  • Rao Asha, then 0 1 2, then 1 2 0
  • Asha Rao, then 3 4 0, then 0 3 4

Mental model. Broad permission is giving the courier your whole key ring. A system picker is opening the door yourself for one parcel, which is safer for you and means the courier never needs the keys.

All three frameworks follow the same pattern: an authorization status you check, a request call that shows the system prompt once, an Info.plist usage description that is shown in that prompt (a missing key crashes the app when the request is made), and increasingly a narrower grant than “everything”. Contacts (CNContactStore): requestAccess(for: .contacts); the status starts at notDetermined; you fetch with unifiedContacts(matching:keysToFetch:), and must name every key you will read, otherwise accessing an unfetched property throws. Format names with CNContactFormatter (a contact with given name Asha and family name Rao formats as Asha Rao, with the locale’s ordering). iOS 18 adds a limited access mode. For picking one or a few people, use the out-of-process CNContactPickerViewController (or ContactAccessButton), which needs no permission at all. EventKit (EKEventStore): since iOS 17 the status has separate levels: writeOnly (4: can add events but not read any) and fullAccess (3), requested with requestWriteOnlyAccessToEvents or requestFullAccessToEvents. If you only add an event, use write-only or the out-of-process EKEventEditViewController, which also needs no permission. Photos (PHPhotoLibrary): requestAuthorization(for: .readWrite), with a limited status (4: the user selected specific photos) in addition to authorized (3) and denied; .addOnly suffices for saving. The out-of-process PHPickerViewController (and SwiftUI PhotosPicker) lets the user pick photos with no permission prompt and returns only what they chose.

The design principle: prefer the system picker to broad authorization. It is safer for the user, nothing to explain, no denied state to design for, and App Review looks favourably on it. When you do need the full library or address book, explain why before asking, support the limited state (show the subset, and offer a way to expand the selection: presentLimitedLibraryPicker), and never gate unrelated features behind access. Handle changes: the user can revoke permission in Settings at any time, so check the status each time you need the data, and observe PHPhotoLibraryChangeObserver or CNContactStoreDidChange and the EventKit change notification to refresh. Treat what you read as sensitive: do not upload address books or photos wholesale, do not log them, store only what you need, and declare the collection in your privacy manifest and App Store privacy labels. Request access at the moment the feature is used, not at launch.

The formatter joins given and family name, EventKit has distinct full (3) and write-only (4) levels, and Photos has a limited status (4).

Common traps

  • Reading a contact property you did not list in keysToFetch. It throws; fetch every key you use.
  • Asking for full photo-library access to let the user choose one picture. Use PHPickerViewController, which needs no permission.

They might follow up with

  • What is write-only calendar access for? Adding events without being able to read the user’s calendar, which is the least privilege for “add to calendar”.
  • How do you handle limited Photos access? Work with the subset, and offer presentLimitedLibraryPicker to change the selection.

Related questions

  • 17.3.2 How does HealthKit handle authorization and units, and what should an app know about Core Motion and sensor…
  • 17.7.1 How do you approach an unfamiliar Apple framework: what do you check before you write code, and how do you…
  • 14.3.1 What is a privacy manifest, what are required-reason APIs, and what must third-party SDKs declare?

How does HealthKit handle authorization and units, and what should an… →