9.9 — Privacy, Permissions & Data Minimization
Opening scenario
App Store Review rejects your submission with: “Your app uses one or more APIs that access user data without including the required Privacy Manifest entries.” Or: “Your NSLocationAlwaysAndWhenInUseUsageDescription string does not adequately describe why your app needs the user’s location.” Privacy enforcement on iOS has moved from polite suggestion to hard gate. This chapter is the playbook for shipping a compliant app — and for being a good steward of user data, which is mostly the same thing.
Context — the iOS privacy stack
| Layer | What it does | Required since |
|---|---|---|
| Usage description strings | Human-readable reason at the OS-level prompt | iOS 6+ |
| App Tracking Transparency (ATT) | Explicit consent for cross-app tracking | iOS 14.5 |
| Privacy Nutrition Labels | Disclosed in App Store listing | Dec 2020 |
| App Privacy Report | User-facing diagnostic showing app data access | iOS 15.2 |
| Required Reason API declarations | Justify use of specific APIs (e.g., file timestamps) | Xcode 15 (May 2024) |
PrivacyInfo.xcprivacy | Manifest of tracked domains + reason APIs | Xcode 15 (May 2024) |
Missing or inaccurate entries lead to App Store Review rejections. Apple has tightened enforcement annually; 2024–2025 reviews catch what passed in 2022.
Usage description strings (Info.plist)
Every permission-gated API requires a corresponding Info.plist string:
| Permission | Key |
|---|---|
| Camera | NSCameraUsageDescription |
| Microphone | NSMicrophoneUsageDescription |
| Photo Library (read) | NSPhotoLibraryUsageDescription |
| Photo Library (add only) | NSPhotoLibraryAddUsageDescription |
| Location (always + in-use) | NSLocationAlwaysAndWhenInUseUsageDescription |
| Location (when-in-use) | NSLocationWhenInUseUsageDescription |
| Contacts | NSContactsUsageDescription |
| Calendar | NSCalendarsUsageDescription |
| Reminders | NSRemindersUsageDescription |
| Bluetooth | NSBluetoothAlwaysUsageDescription |
| Local Network | NSLocalNetworkUsageDescription |
| Tracking (ATT) | NSUserTrackingUsageDescription |
| Face ID | NSFaceIDUsageDescription |
| Speech Recognition | NSSpeechRecognitionUsageDescription |
Quality matters. Vague strings like “We need this for app functionality” get rejected. Strong strings explain the concrete user-facing benefit: “Camera access lets you scan business cards and add contacts automatically.” Apple’s review explicitly judges this.
App Tracking Transparency
If your app uses the IDFA (Identifier for Advertisers) or shares identifiers with third parties for tracking across apps, you must prompt:
import AppTrackingTransparency
import AdSupport
func requestTracking() async {
guard ATTrackingManager.trackingAuthorizationStatus == .notDetermined else { return }
let status = await ATTrackingManager.requestTrackingAuthorization()
switch status {
case .authorized: /* use ASIdentifierManager.shared().advertisingIdentifier */ break
default: /* no tracking */ break
}
}
The prompt is non-customizable. The string above the prompt comes from NSUserTrackingUsageDescription. Do not ask in a pre-prompt that tries to coerce; Apple has rejected apps for doing so. Honor the user’s choice.
ATT consent rates hover around 20-30 % — design your business model assuming most users decline.
PrivacyInfo.xcprivacy (Xcode 15+)
The Privacy Manifest is a .xcprivacy file in your app and in every third-party SDK you bundle. As of May 2024, App Store Review requires it for any app or SDK on Apple’s Commonly Used Reason APIs list.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyTracking</key>
<false/>
<key>NSPrivacyTrackingDomains</key>
<array/>
<key>NSPrivacyCollectedDataTypes</key>
<array>
<dict>
<key>NSPrivacyCollectedDataType</key>
<string>NSPrivacyCollectedDataTypeEmailAddress</string>
<key>NSPrivacyCollectedDataTypeLinked</key>
<true/>
<key>NSPrivacyCollectedDataTypeTracking</key>
<false/>
<key>NSPrivacyCollectedDataTypePurposes</key>
<array>
<string>NSPrivacyCollectedDataTypePurposeAppFunctionality</string>
</array>
</dict>
</array>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>CA92.1</string> <!-- Access info for current user only -->
</array>
</dict>
</array>
</dict>
</plist>
Required Reason API categories (current list, may grow):
- File timestamp APIs (
creationDate,modificationDate) - System boot time
- Disk space
- Active keyboard
UserDefaults
Each requires a numeric reason code documented in Apple’s list. Pick the most specific reason that matches your use; don’t pick a permissive one to “cover yourself.”
For tracking, list every domain involved in cross-app/cross-site tracking under NSPrivacyTrackingDomains. iOS 17+ silently blocks DNS resolution for unlisted tracking domains when ATT consent is denied. Missing entries = silent broken functionality.
Privacy Nutrition Labels
In App Store Connect, declare what data your app collects and how it’s used. Categories:
- Identifiers — user ID, device ID
- Usage Data — interactions, ad clicks
- Diagnostics — crash data, performance data
- Location — coarse, precise
- Contact Info — name, email, phone, address
- User Content — messages, photos
- Financial Info — payment info, credit info
- Health & Fitness
- Sensitive Info
- Browsing History
- Search History
- Purchases
- Contacts
- Other Data
For each: is it linked to user identity? Used for tracking? What’s the purpose (analytics, product personalization, app functionality, third-party advertising)?
The labels appear in the App Store listing. Inaccurate labels are a violation of the Developer Program License Agreement and can trigger app removal. Audit yearly and after every SDK addition.
App Privacy Report (iOS 15.2+)
Users can enable “Record App Activity” in Settings → Privacy → App Privacy Report. The OS logs:
- Permissions accessed (camera, mic, location, contacts, etc.) with timestamp
- Network domains contacted
- Domains contacted by app in the last 7 days
Users see your app’s data access in plain language. If your nutrition label says “we don’t collect location” but the privacy report shows daily location pings, users notice — and complain publicly. The report is a forcing function for honesty.
Data minimization principles
The lasting protection is collecting less data in the first place:
- Don’t ask if you don’t need. Every permission prompt has friction; every unused permission is liability.
- Coarse over precise. Location: ask for “while using” not “always”; precision: reduced (city-level) not full unless you need it.
- Local over server. Process on-device when possible (Core ML, on-device speech, local search) instead of sending data up.
- Ephemeral over persistent. Keep what you need for the session; don’t archive what you don’t need to recall.
- Hash or tokenize identifiers. If you only need to know “same user across requests,” send a hash of the user ID, not the ID itself.
- Delete on demand. Implement actual data deletion (not just account deactivation) within 30 days of request — GDPR and CCPA require it; the App Store requires an in-app delete-account flow.
GDPR & CCPA touchpoints
Compliance is mostly back-end policy, but client-side concerns:
- Consent flow for non-essential data collection in the EU/UK
- Privacy policy linked from Settings and the App Store listing
- Data export — give users a download of their data (often a server endpoint surfaced in-app)
- Account deletion — Apple requires this in-app since iOS 16 / June 2022
- Children’s data (COPPA, GDPR-K) — different rules under 13/16; if applicable, age-gate
A senior engineer doesn’t need to write the privacy policy but must know what features the lawyers’ requirements translate to in code.
In the wild
Apple’s first-party apps (Notes, Maps, Photos) demonstrate the gold standard — minimal data collection, all on-device processing where possible, clear and specific usage strings. DuckDuckGo and Firefox Focus market themselves on the same principles, with extensive NSPrivacyTrackingDomains blocklists and zero linked-identifier data types in their nutrition labels.
Common misconceptions
- “Privacy is just legal stuff.” It’s also a hard App Store Review gate and a public reputation issue. Engineers own the implementation.
- “We don’t need a Privacy Manifest because we’re a simple app.” If you use
UserDefaultsor file timestamps, you need one. That’s almost every app. - “Vague usage strings are safer because they don’t commit to anything.” Apple Review rejects vague strings. Be specific or be rejected.
- “ATT prompt is optional.” Required if you use IDFA or share any tracking identifier. Missing prompt + tracking = removal.
- “Account deletion via email is fine.” Apple requires in-app deletion since iOS 16. Email-only is a rejection.
Seasoned engineer’s take
Privacy is increasingly a technical discipline, not just a policy one. The Privacy Manifest, ATT prompts, App Privacy Report, and Required Reason APIs are all implemented in code. Treat them as architecture concerns from day one of a new project — retrofitting compliance into a shipped app costs weeks and risks app removal. The good news: every privacy-respecting design choice (data minimization, on-device processing, ephemeral storage) also reduces breach blast radius and infrastructure cost. Privacy and engineering quality converge.
TIP: Add
PrivacyInfo.xcprivacyto your starter template before writing any feature code. Each new dependency or API addition triggers a manifest review. Cheap when habitual; expensive as a pre-submission scramble.
WARNING: Don’t lie on nutrition labels to look better. The App Privacy Report makes lies user-visible; the discrepancy is a guaranteed PR story.
Interview corner
Junior: “What’s NSCameraUsageDescription?”
The Info.plist string shown to the user when your app first requests camera access. Required to use the camera; must be specific and user-meaningful or App Store Review rejects.
Mid: “What’s the Privacy Manifest and what does it contain?”
A PrivacyInfo.xcprivacy file required for apps and SDKs since Xcode 15. Declares tracked domains, collected data types and purposes, and required-reason API usage (UserDefaults, file timestamps, etc.). Missing or inaccurate manifests cause App Store Review rejections.
Senior: “Design a privacy strategy for a new health-tracking app.”
Start from data minimization: process everything on-device with Core ML and the Health framework, sync only aggregates upward (and only if the user opts in). All permission strings written by the product team with concrete user-facing reasons. Manifest declares the minimum data types (probably Health & Fitness and Diagnostics) with Linked = false where possible. No ATT prompt because we don’t track. No third-party analytics SDKs in the initial release — own the metrics via first-party logging with strict PII filtering. Privacy policy reviewed by counsel and surfaced in Settings + onboarding. In-app account deletion flow that actually deletes server data within 30 days (GDPR/CCPA). Quarterly review walks the App Privacy Report on internal devices to verify our claims match reality. The architecture saves on backend costs, simplifies HIPAA conversations if we ever go US-clinical, and avoids the entire class of “you said you didn’t collect X but you do” disasters.
Red-flag answer: “Legal will handle that.” Reveals offloading of engineering responsibility.
Lab preview
Lab 9.1 includes adding a complete PrivacyInfo.xcprivacy to the starter notes app, with accurate data type declarations and Required Reason API entries.