WWDC Quick Look đź’“ By SwiftGGTeam
Integrate privacy into your development process

Integrate privacy into your development process

Watch original video

Highlight

Apple privacy engineer Joey Tyson breaks app development into five stages — planning, design, development, testing, deployment — and gives the matching privacy actions and system APIs for each.


Core Content

Many developers only start thinking about privacy a week before launch: how many permission prompts to show, how to fill out the Privacy Nutrition Label, whether the third-party SDKs carry compliance risk. This “patch it afterwards” stance forces you to compromise — the architecture is set, the data already crosses several contexts, and there is no way to add end-to-end encryption or move work on-device without major surgery. Joey Tyson’s core argument in this session is the same point that applies to security and localization: privacy cannot be retrofitted late, you have to step in before the first line of code.

He splits app development into five stages — planning, design, development, testing, deployment — and gives concrete privacy actions for each. The planning stage produces a privacy assurance, written in plain language, that says how the app will handle user data. The design stage translates those assurances into UI the user can see: a first-launch summary, status-change prompts, contextual choices. The development stage uses system capabilities like PhotosPicker, Location Button, CloudKit encryptedValues, PIR, and AdAttributionKit to put the assurances into code. The testing stage writes unit, integration, and UI tests to verify the assurances still hold. The deployment stage fills out the Privacy Nutrition Label and runs Xcode’s “Generate Privacy Report” at archive time to roll up every privacy manifest. The benefit of this flow is that every engineering decision traces back to an original privacy assurance, so questions like “why are we uploading location to the server?” never surface for the first time during review.

The speaker also keeps coming back to a counterintuitive point: cutting permission prompts is itself privacy design. System pickers — PhotosPicker, ContactPicker, Location Button — render in the system process, so the user’s selection already implies consent and no prompt is needed. This is both a better experience and better privacy than asking for the whole photo library and then letting the user pick.


Detailed Content

Planning: write privacy assurances using the four pillars (03:33)

Apple uses four pillars internally to guide writing privacy assurances: data minimization, on-device processing, transparency and control, and security protections. The speaker walks through each one with a fictional app called “Pal About”:

  • Data minimization → “We only retain aggregate usage data.”
  • Strong defaults → “When searching for nearby places, your current location is not stored by default.”
  • On-device processing → “Suggested meetup locations are only generated locally using on-device data.”
  • Transparency and control → “Photos you upload are only used to train generative models if you opt in to improve intelligence features.”
  • Security protections → “We cannot read messages to your friends in transit between devices.” (Backed by end-to-end encryption at the architecture level.)

These statements turn marketing language into engineering requirements: each one maps to a concrete technical choice.

Development: replace permission prompts with system pickers (10:29)

PhotosPicker renders in the system process. The app receives only the photos the user actually picked, and no photo library permission is needed:

// Define the app's Photos picker
PhotosPicker(
    selection: $viewModel.selection,
    matching: .images,
    preferredItemEncoding: .current,
    photoLibrary: .shared()
) {
    Text("Select Photos")
}

// Configure a half-height Photos picker
.photosPickerStyle(.inline)
.ignoresSafeArea()
.frame(height: 340)

Key points:

  • selection: $viewModel.selection binds to a state value; only the photos the user actively picks flow back into the app.
  • matching: .images restricts the media type, so the picker hides videos.
  • photoLibrary: .shared() uses the system’s shared photo library handle. The whole selection runs in the system process, and the app never touches the library directly.
  • .photosPickerStyle(.inline) embeds the picker in the current UI instead of presenting it full-screen; with .frame(height: 340) it shows at half height.
  • Because no photo library permission API is called, no extra prompt appears beyond App Tracking.

Development: Location Button for one-shot location sharing (11:33)

LocationButton(LocationButton.Title.currentLocation) {
    // Start updating location when user taps the button.
    // Location button doesn't require the additional
    // step of calling 'requestWhenInUseAuthorization()'.
    manager.startUpdatingLocation()
}.foregroundColor(Color.white)
    .cornerRadius(27)
    .frame(width: 210, height: 54)
    .padding(.bottom, 30)

Key points:

  • LocationButton(LocationButton.Title.currentLocation) uses a system-supplied title. The system verifies that the button was actually tapped by the user, so the app cannot fake a tap.
  • The closure calls manager.startUpdatingLocation() directly. There is no need to call requestWhenInUseAuthorization() first, which removes one permission prompt.
  • On the first tap the system shows a one-time confirmation explaining what the button does. Later taps share the current location without any prompt.
  • The location indicator still appears at the top of the screen, so the user always knows location is in use.
  • Style — color, corner radius, size — is customizable to match the app’s look.

Development: field-level encryption in CloudKit (13:48)

myRecord.encryptedValues["encryptedStringField"] = "Sensitive value"

let decryptedString = myRecord.encryptedValues["encryptedStringField"] as? String

Key points:

  • encryptedValues is a dictionary property on CKRecord. The system encrypts on assignment and decrypts on read; the developer never handles keys.
  • The matching requirement is in the CloudKit schema: set the field type to an encrypted variant such as EncryptedString or EncryptedBytes. CKAsset is encrypted by default.
  • When the user turns on Advanced Data Protection, these fields are upgraded to end-to-end encryption and the server cannot read the plaintext.
  • Encrypted fields cannot be indexed, so they cannot appear in a CKQuery predicate or sortDescriptors — the query will fail.

Development: PIR hides query content from the server (14:22)

Homomorphic encryption lets you add and multiply ciphertext without decrypting it. Private Information Retrieval (PIR) is built on top of it: the device encrypts the query and sends it to the server, the server computes the encrypted result on the ciphertext using homomorphic encryption and sends it back, and the device decrypts locally. Throughout the flow the server never sees what the query is or which record was matched. PIR is already in use inside Apple’s own products. The open-source code lives at apple/swift-homomorphic-encryption.

Development: cut down on identifying data collection (16:05)

  • Private Access Tokens replace CAPTCHA. After device attestation the device gets an anonymous token; the server validates the token without needing identity.
  • DeviceCheck binds up to 2 bits of state to a device (for example, “has redeemed the new-user offer”). Apple stores the state, so it survives reinstall and device migration, but the app never sees a device identifier.
  • AdAttributionKit does attribution through device-signed postbacks, with no App Tracking Transparency prompt.

Testing: turn privacy assurances into test cases (20:50)

  • Unit tests cover individual functions tied to privacy controls.
  • Integration tests cover data flow between subsystems — for example, that CloudKit encrypted-field reads and writes still work.
  • UI tests confirm that when the user toggles an opt-in, the data flow actually changes.

Since iOS 15.2, you can turn on App Privacy Report in Settings and check the app’s data access, sensor use, and network activity yourself, to make sure it matches what the user expects.

Deployment: Privacy Nutrition Label (22:28)

Fill out the nutrition label in the App Privacy section of App Store Connect, describing which data the app sends off the device and what it is used for. When archiving in Xcode, choose Generate Privacy Report from the archive context menu. It rolls up every privacy manifest, including those from third-party SDKs, and produces a report you can use to confirm the nutrition label is complete. The label must list every potential use, even if the user can turn off a particular collection. The label can be updated at any time without shipping a new build.


Core Takeaways

1. Add a “privacy assurance” section to your kickoff doc

Why it is worth doing: many teams’ PRDs describe features but not data contracts, and end up storing data that should never have been stored. Writing one line up front — “the user’s location is not uploaded by default” — gives every later architecture decision an anchor.

How to start: use Apple’s four pillars as a checklist — data minimization, on-device processing, transparency and control, security protections. Write one or two concrete, user-readable promises under each pillar.

2. Replace broad permission APIs with system pickers

Why it is worth doing: every permission prompt is a place where the user might say no. System components like PhotosPicker, ContactPicker, Location Button, and UIPasteControl show no prompt at all — better experience, better privacy, and fewer access entries in the App Privacy Report.

How to start: grep the codebase for PHPhotoLibrary.requestAuthorization, requestWhenInUseAuthorization, requestRecordPermission, and see which ones can move to a picker. For new code, reach for a picker first and only fall back to a permission API if the picker cannot do the job.

3. Write a UI test for each privacy assurance

Why it is worth doing: privacy issues are most expensive when they surface during review (you may have to redo the architecture). One UI test that asserts “after the user turns off the opt-in, this endpoint is no longer called” makes regressions a CI failure instead of a launch-week scramble.

How to start: pick the most important assurance — for example, “no photo upload for model training without opt-in.” Use XCUITest to toggle the switch off, trigger the upload flow, and assert that the network request or analytics event did not fire.

4. Use CloudKit encryptedValues instead of rolling your own encryption

Why it is worth doing: managing keys, rotation, and backup yourself is a deep hole. CloudKit encrypted fields combined with Advanced Data Protection give you end-to-end encryption with almost no integration cost.

How to start: change sensitive field types in the schema to EncryptedString or EncryptedBytes. Read and write through record.encryptedValues["field"] in code. Remove any use of these fields as predicates or sort keys in CKQuery.

5. Run Generate Privacy Report before archiving

Why it is worth doing: third-party SDK privacy manifests change often, and reconciling them by hand is error-prone. Xcode’s Generate Privacy Report rolls up every manifest with one click and lines them up against the nutrition label.

How to start: add “after archiving, open the Privacy Report and reconcile against the nutrition label” to your release checklist. If a third-party SDK has no privacy manifest, file an issue against the library, and if the maintainers do not respond, plan to swap it out.


Comments

GitHub Issues · utterances