iOS Privacy Permissions Guide
iOS Privacy & Permissions

Best practices for permission requests, privacy UI, and building user trust.
# iOS Privacy & Permissions Best practices for permission requests, privacy UI, and building user trust. ## Table of Contents 1. [Permission Request Philosophy](#permission-request-philosophy) 2. [Permission Request Timing](#permission-request-timing) 3. [Permission Types & Best Practices](#permission-types-best-practices) 4. [Handling Denied Permissions](#handling-denied-permissions) 5. [Privacy UI Patterns](#privacy-ui-patterns) 6. [App Privacy Labels](#app-privacy-labels) 7. [Usage String Best Practices](#usage-string-best-practices) 8. [Testing Permissions](#testing-permissions) --- ## Permission Request Philosophy
Overview
Best practices for permission requests, privacy UI, and building user trust.
Manual install (npx, ZIP)
npx skills add wondelai/skills --skill ios-privacy-permissions -g -a claude-codeConnects with Agent Alley MCP · agentalley.io/mcp
Files included
privacy-permissions.md13.5 KB
Read privacy-permissions.md here
wondelai/
Show the full text456 lines
iOS Privacy & Permissions
Best practices for permission requests, privacy UI, and building user trust.
Table of Contents
- Permission Request Philosophy
- Permission Request Timing
- Permission Types & Best Practices
- Handling Denied Permissions
- Privacy UI Patterns
- App Privacy Labels
- Usage String Best Practices
- Testing Permissions
Permission Request Philosophy
Core principle: Request permissions only when needed, explain why, and respect "no."
Users are increasingly permission-fatigued. Every unnecessary or poorly-timed request damages trust and increases denial rates.
Permission Request Timing
Just-In-Time Requests
Request permissions when the user takes an action that requires them, not at app launch.
Bad: Request camera permission on first launch Good: Request camera permission when user taps "Take Photo"
The Pre-Permission Pattern
Before the system dialog, show a custom screen explaining the value.
┌─────────────────────────────────────────┐
│ │
│ [Camera icon] │
│ │
│ Take photos of your receipts │
│ │
│ We use your camera to quickly │
│ scan and organize your expenses. │
│ Photos are stored only on your │
│ device. │
│ │
│ ┌─────────────────────────────────┐ │
│ │ Allow Camera │ │
│ └─────────────────────────────────┘ │
│ │
│ Maybe Later │
│ │
└─────────────────────────────────────────┘
Benefits:
- Explains value before system dialog
- "Maybe Later" doesn't trigger system denial
- Higher acceptance rates
- Better user understanding
System Permission Dialog
// Camera
AVCaptureDevice.requestAccess(for: .video) { granted in
// Handle response
}
// Photos
PHPhotoLibrary.requestAuthorization(for: .readWrite) { status in
// Handle status
}
// Location
locationManager.requestWhenInUseAuthorization()
// or
locationManager.requestAlwaysAuthorization()
// Notifications
UNUserNotificationCenter.current().requestAuthorization(
options: [.alert, .badge, .sound]
) { granted, error in
// Handle response
}
Permission Types & Best Practices
Camera
When to request: When user initiates camera action
Usage string example: "[App] needs camera access to scan documents and take photos for your projects."
Best practices:
- Only request when camera feature is used
- Offer photo library as alternative
- Handle denial gracefully (show library option)
Photo Library
Access levels (iOS 14+):
.addOnly- Can add photos, can't read (for saving).readWrite- Full access- Limited selection - User picks specific photos
When to request: When user wants to access photos
Usage string example: "[App] accesses your photos to let you add images to your posts."
Best practices:
- Request
.addOnlyif you only need to save - Support limited photo selection (don't require full access)
- Use PHPicker for one-time selection (no permission needed)
// PHPicker - no permission required
var config = PHPickerConfiguration()
config.selectionLimit = 1
config.filter = .images
let picker = PHPickerViewController(configuration: config)
Location
Authorization levels:
.whenInUse- Only while app is active.always- Background location access
When to request: When location feature is needed
Usage strings needed:
NSLocationWhenInUseUsageDescriptionNSLocationAlwaysAndWhenInUseUsageDescription(for always)
Best practices:
- Start with "When In Use" before requesting "Always"
- Explain why background location is needed
- Provide value even without location
- Use significant location changes if precise tracking unnecessary
// Request when in use first
locationManager.requestWhenInUseAuthorization()
// Later, if needed, escalate to always
// (triggers new system prompt explaining upgrade)
locationManager.requestAlwaysAuthorization()
Notifications
When to request: After user has experienced app value
Usage string example: "[App] sends notifications for messages from your team and important updates."
Best practices:
- Don't request on first launch
- Wait until user has seen value
- Explain what notifications they'll receive
- Provide in-app notification preferences
- Respect system settings
Provisional notifications (iOS 12+):
// Quietly delivered to Notification Center
// User can choose to keep or turn off
UNUserNotificationCenter.current().requestAuthorization(
options: [.provisional, .alert, .sound]
) { granted, error in }
Contacts
When to request: When user initiates contact-related feature
Usage string example: "[App] accesses your contacts to help you invite friends and find people you know."
Best practices:
- Use CNContactPickerViewController when possible (no permission)
- Only request full access when truly needed
- Never sync contacts without explicit permission
Microphone
When to request: When user initiates audio recording
Usage string example: "[App] uses your microphone to record voice messages and audio notes."
Best practices:
- Clear indicator when recording
- Option to preview before sending
- Explain storage/transmission of audio
Health Data
When to request: When user enables health features
Best practices:
- Request only specific data types needed
- Explain how data will be used
- Provide value without health access
- Handle partial authorization
Tracking (ATT)
When to request: Before tracking user across apps
Required prompt:
ATTrackingManager.requestTrackingAuthorization { status in
switch status {
case .authorized:
// Enable tracking
case .denied, .restricted:
// Disable tracking
case .notDetermined:
// Request hasn't been shown yet
}
}
Best practices:
- Explain value of personalized ads first
- Don't punish users who decline
- App must function without tracking
Handling Denied Permissions
Graceful Degradation
Always provide alternative paths when permission is denied.
| Permission Denied | Alternative |
|---|---|
| Camera | Photo library option |
| Location | Manual address entry |
| Notifications | In-app message center |
| Contacts | Manual contact entry |
| Photos | Camera-only option |
Re-Requesting After Denial
Once denied, system won't show prompt again. Guide users to Settings.
func openAppSettings() {
guard let settingsUrl = URL(string: UIApplication.openSettingsURLString),
UIApplication.shared.canOpenURL(settingsUrl) else {
return
}
UIApplication.shared.open(settingsUrl)
}
UI pattern:
┌─────────────────────────────────────────┐
│ │
│ Camera Access Needed │
│ │
│ To scan documents, allow camera │
│ access in Settings. │
│ │
│ ┌─────────────────────────────────┐ │
│ │ Open Settings │ │
│ └─────────────────────────────────┘ │
│ │
│ Not Now │
│ │
└─────────────────────────────────────────┘
Don't Ask Again (Within Session)
If user taps "Maybe Later" on pre-permission screen, don't immediately ask again.
// Track dismissal
UserDefaults.standard.set(Date(), forKey: "camera_prompt_dismissed")
// Wait before showing again
func shouldShowCameraPrompt() -> Bool {
guard let lastDismissed = UserDefaults.standard.object(
forKey: "camera_prompt_dismissed"
) as? Date else {
return true
}
// Wait at least 3 days
return Date().timeIntervalSince(lastDismissed) > 3 * 24 * 60 * 60
}
Privacy UI Patterns
Permission Status Indicators
Show current permission state in settings:
┌─────────────────────────────────────────┐
│ Permissions │
├─────────────────────────────────────────┤
│ 📷 Camera Allowed ▶ │
│ 📍 Location While Using▶ │
│ 🔔 Notifications Off ▶ │
│ 📱 Contacts Not Asked ▶ │
└─────────────────────────────────────────┘
Data Usage Transparency
Explain what data is collected and why:
┌─────────────────────────────────────────┐
│ Privacy │
├─────────────────────────────────────────┤
│ │
│ Data We Collect │
│ │
│ • Usage analytics │
│ To improve app performance │
│ │
│ • Crash reports │
│ To fix bugs and issues │
│ │
│ • Photos you upload │
│ Stored securely on our servers │
│ │
│ [View Privacy Policy] │
│ │
└─────────────────────────────────────────┘
Data Deletion Options
Provide clear data management:
┌─────────────────────────────────────────┐
│ Your Data │
├─────────────────────────────────────────┤
│ Download My Data ▶ │
│ Delete My Account ▶ │
└─────────────────────────────────────────┘
App Privacy Labels
Required Categories
Your App Store listing must declare:
Data Used to Track You
- Data used for advertising across apps
Data Linked to You
- Identifiable data (name, email, etc.)
Data Not Linked to You
- Anonymous analytics, crash data
Best Practices
- Be accurate—Apple verifies
- Minimize collection to reduce label size
- Simpler labels build trust
- Update when collection changes
Usage String Best Practices
Structure
"[App name] [action] to [user benefit]."
Examples by Permission
| Permission | Good Example |
|---|---|
| Camera | "MyApp uses the camera to scan barcodes for quick product lookup." |
| Photos | "MyApp saves photos you create to your photo library." |
| Location | "MyApp uses your location to show nearby restaurants and estimated delivery times." |
| Microphone | "MyApp uses the microphone to record voice notes for your journal entries." |
| Contacts | "MyApp accesses contacts to help you split bills with friends." |
What to Avoid
- Generic explanations ("to improve your experience")
- Technical jargon
- Mentioning advertising without explaining value
- Being vague about data use
Testing Permissions
Reset Permissions
# Reset all permissions for specific app
xcrun simctl privacy booted reset all com.yourapp.bundleid
# Reset specific permission
xcrun simctl privacy booted reset camera com.yourapp.bundleid
Test All States
For each permission:
- Never requested (first launch)
- Authorized
- Denied
- Restricted (parental controls)
- Limited (Photos)
- Provisional (Notifications)
Automated Testing
func testCameraPermissionDenied() {
// Set up mock authorization status
mockCameraAuthorization = .denied
// Trigger camera feature
app.buttons["Take Photo"].tap()
// Verify fallback UI appears
XCTAssertTrue(app.staticTexts["Camera access needed"].exists)
XCTAssertTrue(app.buttons["Open Settings"].exists)
}
| 1 | # iOS Privacy & Permissions |
| 2 | |
| 3 | Best practices for permission requests, privacy UI, and building user trust. |
| 4 | |
| 5 | |
| 6 | ## Table of Contents |
| 7 | [Permission Request Philosophy] |
| 8 | [Permission Request Timing] |
| 9 | [Permission Types & Best Practices] |
| 10 | [Handling Denied Permissions] |
| 11 | [Privacy UI Patterns] |
| 12 | [App Privacy Labels] |
| 13 | [Usage String Best Practices] |
| 14 | [Testing Permissions] |
| 15 | |
| 16 | |
| 17 | |
| 18 | ## Permission Request Philosophy |
| 19 | |
| 20 | **Core principle:** Request permissions only when needed, explain why, and respect "no." |
| 21 | |
| 22 | Users are increasingly permission-fatigued. Every unnecessary or poorly-timed request damages trust and increases denial rates. |
| 23 | |
| 24 | |
| 25 | |
| 26 | ## Permission Request Timing |
| 27 | |
| 28 | ### Just-In-Time Requests |
| 29 | |
| 30 | Request permissions when the user takes an action that requires them, not at app launch. |
| 31 | |
| 32 | **Bad:** Request camera permission on first launch |
| 33 | **Good:** Request camera permission when user taps "Take Photo" |
| 34 | |
| 35 | ### The Pre-Permission Pattern |
| 36 | |
| 37 | Before the system dialog, show a custom screen explaining the value. |
| 38 | |
| 39 | |
| 40 | ┌─────────────────────────────────────────┐ |
| 41 | │ │ |
| 42 | │ [Camera icon] │ |
| 43 | │ │ |
| 44 | │ Take photos of your receipts │ |
| 45 | │ │ |
| 46 | │ We use your camera to quickly │ |
| 47 | │ scan and organize your expenses. │ |
| 48 | │ Photos are stored only on your │ |
| 49 | │ device. │ |
| 50 | │ │ |
| 51 | │ ┌─────────────────────────────────┐ │ |
| 52 | │ │ Allow Camera │ │ |
| 53 | │ └─────────────────────────────────┘ │ |
| 54 | │ │ |
| 55 | │ Maybe Later │ |
| 56 | │ │ |
| 57 | └─────────────────────────────────────────┘ |
| 58 | |
| 59 | |
| 60 | **Benefits:** |
| 61 | Explains value before system dialog |
| 62 | "Maybe Later" doesn't trigger system denial |
| 63 | Higher acceptance rates |
| 64 | Better user understanding |
| 65 | |
| 66 | ### System Permission Dialog |
| 67 | |
| 68 | |
| 69 | // Camera |
| 70 | AVCaptureDevice.requestAccess(for: .video) { granted in |
| 71 | // Handle response |
| 72 | } |
| 73 | |
| 74 | // Photos |
| 75 | PHPhotoLibrary.requestAuthorization(for: .readWrite) { status in |
| 76 | // Handle status |
| 77 | } |
| 78 | |
| 79 | // Location |
| 80 | locationManager.requestWhenInUseAuthorization() |
| 81 | // or |
| 82 | locationManager.requestAlwaysAuthorization() |
| 83 | |
| 84 | // Notifications |
| 85 | UNUserNotificationCenter.current().requestAuthorization( |
| 86 | options: [.alert, .badge, .sound] |
| 87 | ) { granted, error in |
| 88 | // Handle response |
| 89 | } |
| 90 | |
| 91 | |
| 92 | |
| 93 | |
| 94 | ## Permission Types & Best Practices |
| 95 | |
| 96 | ### Camera |
| 97 | |
| 98 | **When to request:** When user initiates camera action |
| 99 | |
| 100 | **Usage string example:** |
| 101 | "[App] needs camera access to scan documents and take photos for your projects." |
| 102 | |
| 103 | **Best practices:** |
| 104 | Only request when camera feature is used |
| 105 | Offer photo library as alternative |
| 106 | Handle denial gracefully (show library option) |
| 107 | |
| 108 | ### Photo Library |
| 109 | |
| 110 | **Access levels (iOS 14+):** |
| 111 | `.addOnly` - Can add photos, can't read (for saving) |
| 112 | `.readWrite` - Full access |
| 113 | Limited selection - User picks specific photos |
| 114 | |
| 115 | **When to request:** When user wants to access photos |
| 116 | |
| 117 | **Usage string example:** |
| 118 | "[App] accesses your photos to let you add images to your posts." |
| 119 | |
| 120 | **Best practices:** |
| 121 | Request `.addOnly` if you only need to save |
| 122 | Support limited photo selection (don't require full access) |
| 123 | Use PHPicker for one-time selection (no permission needed) |
| 124 | |
| 125 | |
| 126 | // PHPicker - no permission required |
| 127 | var config = PHPickerConfiguration() |
| 128 | config.selectionLimit = 1 |
| 129 | config.filter = .images |
| 130 | |
| 131 | let picker = PHPickerViewController(configuration: config) |
| 132 | |
| 133 | |
| 134 | ### Location |
| 135 | |
| 136 | **Authorization levels:** |
| 137 | `.whenInUse` - Only while app is active |
| 138 | `.always` - Background location access |
| 139 | |
| 140 | **When to request:** When location feature is needed |
| 141 | |
| 142 | **Usage strings needed:** |
| 143 | `NSLocationWhenInUseUsageDescription` |
| 144 | `NSLocationAlwaysAndWhenInUseUsageDescription` (for always) |
| 145 | |
| 146 | **Best practices:** |
| 147 | Start with "When In Use" before requesting "Always" |
| 148 | Explain why background location is needed |
| 149 | Provide value even without location |
| 150 | Use significant location changes if precise tracking unnecessary |
| 151 | |
| 152 | |
| 153 | // Request when in use first |
| 154 | locationManager.requestWhenInUseAuthorization() |
| 155 | |
| 156 | // Later, if needed, escalate to always |
| 157 | // (triggers new system prompt explaining upgrade) |
| 158 | locationManager.requestAlwaysAuthorization() |
| 159 | |
| 160 | |
| 161 | ### Notifications |
| 162 | |
| 163 | **When to request:** After user has experienced app value |
| 164 | |
| 165 | **Usage string example:** |
| 166 | "[App] sends notifications for messages from your team and important updates." |
| 167 | |
| 168 | **Best practices:** |
| 169 | Don't request on first launch |
| 170 | Wait until user has seen value |
| 171 | Explain what notifications they'll receive |
| 172 | Provide in-app notification preferences |
| 173 | Respect system settings |
| 174 | |
| 175 | **Provisional notifications (iOS 12+):** |
| 176 | |
| 177 | // Quietly delivered to Notification Center |
| 178 | // User can choose to keep or turn off |
| 179 | UNUserNotificationCenter.current().requestAuthorization( |
| 180 | options: [.provisional, .alert, .sound] |
| 181 | ) { granted, error in } |
| 182 | |
| 183 | |
| 184 | ### Contacts |
| 185 | |
| 186 | **When to request:** When user initiates contact-related feature |
| 187 | |
| 188 | **Usage string example:** |
| 189 | "[App] accesses your contacts to help you invite friends and find people you know." |
| 190 | |
| 191 | **Best practices:** |
| 192 | Use CNContactPickerViewController when possible (no permission) |
| 193 | Only request full access when truly needed |
| 194 | Never sync contacts without explicit permission |
| 195 | |
| 196 | ### Microphone |
| 197 | |
| 198 | **When to request:** When user initiates audio recording |
| 199 | |
| 200 | **Usage string example:** |
| 201 | "[App] uses your microphone to record voice messages and audio notes." |
| 202 | |
| 203 | **Best practices:** |
| 204 | Clear indicator when recording |
| 205 | Option to preview before sending |
| 206 | Explain storage/transmission of audio |
| 207 | |
| 208 | ### Health Data |
| 209 | |
| 210 | **When to request:** When user enables health features |
| 211 | |
| 212 | **Best practices:** |
| 213 | Request only specific data types needed |
| 214 | Explain how data will be used |
| 215 | Provide value without health access |
| 216 | Handle partial authorization |
| 217 | |
| 218 | ### Tracking (ATT) |
| 219 | |
| 220 | **When to request:** Before tracking user across apps |
| 221 | |
| 222 | **Required prompt:** |
| 223 | |
| 224 | ATTrackingManager.requestTrackingAuthorization { status in |
| 225 | switch status { |
| 226 | case .authorized: |
| 227 | // Enable tracking |
| 228 | case .denied, .restricted: |
| 229 | // Disable tracking |
| 230 | case .notDetermined: |
| 231 | // Request hasn't been shown yet |
| 232 | } |
| 233 | } |
| 234 | |
| 235 | |
| 236 | **Best practices:** |
| 237 | Explain value of personalized ads first |
| 238 | Don't punish users who decline |
| 239 | App must function without tracking |
| 240 | |
| 241 | |
| 242 | |
| 243 | ## Handling Denied Permissions |
| 244 | |
| 245 | ### Graceful Degradation |
| 246 | |
| 247 | Always provide alternative paths when permission is denied. |
| 248 | |
| 249 | | Permission Denied | Alternative | |
| 250 | |-------------------|-------------| |
| 251 | | Camera | Photo library option | |
| 252 | | Location | Manual address entry | |
| 253 | | Notifications | In-app message center | |
| 254 | | Contacts | Manual contact entry | |
| 255 | | Photos | Camera-only option | |
| 256 | |
| 257 | ### Re-Requesting After Denial |
| 258 | |
| 259 | Once denied, system won't show prompt again. Guide users to Settings. |
| 260 | |
| 261 | |
| 262 | func openAppSettings() { |
| 263 | guard let settingsUrl = URL(string: UIApplication.openSettingsURLString), |
| 264 | UIApplication.shared.canOpenURL(settingsUrl) else { |
| 265 | return |
| 266 | } |
| 267 | UIApplication.shared.open(settingsUrl) |
| 268 | } |
| 269 | |
| 270 | |
| 271 | **UI pattern:** |
| 272 | |
| 273 | ┌─────────────────────────────────────────┐ |
| 274 | │ │ |
| 275 | │ Camera Access Needed │ |
| 276 | │ │ |
| 277 | │ To scan documents, allow camera │ |
| 278 | │ access in Settings. │ |
| 279 | │ │ |
| 280 | │ ┌─────────────────────────────────┐ │ |
| 281 | │ │ Open Settings │ │ |
| 282 | │ └─────────────────────────────────┘ │ |
| 283 | │ │ |
| 284 | │ Not Now │ |
| 285 | │ │ |
| 286 | └─────────────────────────────────────────┘ |
| 287 | |
| 288 | |
| 289 | ### Don't Ask Again (Within Session) |
| 290 | |
| 291 | If user taps "Maybe Later" on pre-permission screen, don't immediately ask again. |
| 292 | |
| 293 | |
| 294 | // Track dismissal |
| 295 | UserDefaults.standard.set(Date(), forKey: "camera_prompt_dismissed") |
| 296 | |
| 297 | // Wait before showing again |
| 298 | func shouldShowCameraPrompt() -> Bool { |
| 299 | guard let lastDismissed = UserDefaults.standard.object( |
| 300 | forKey: "camera_prompt_dismissed" |
| 301 | ) as? Date else { |
| 302 | return true |
| 303 | } |
| 304 | // Wait at least 3 days |
| 305 | return Date().timeIntervalSince(lastDismissed) > 3 * 24 * 60 * 60 |
| 306 | } |
| 307 | |
| 308 | |
| 309 | |
| 310 | |
| 311 | ## Privacy UI Patterns |
| 312 | |
| 313 | ### Permission Status Indicators |
| 314 | |
| 315 | Show current permission state in settings: |
| 316 | |
| 317 | |
| 318 | ┌─────────────────────────────────────────┐ |
| 319 | │ Permissions │ |
| 320 | ├─────────────────────────────────────────┤ |
| 321 | │ 📷 Camera Allowed ▶ │ |
| 322 | │ 📍 Location While Using▶ │ |
| 323 | │ 🔔 Notifications Off ▶ │ |
| 324 | │ 📱 Contacts Not Asked ▶ │ |
| 325 | └─────────────────────────────────────────┘ |
| 326 | |
| 327 | |
| 328 | ### Data Usage Transparency |
| 329 | |
| 330 | Explain what data is collected and why: |
| 331 | |
| 332 | |
| 333 | ┌─────────────────────────────────────────┐ |
| 334 | │ Privacy │ |
| 335 | ├─────────────────────────────────────────┤ |
| 336 | │ │ |
| 337 | │ Data We Collect │ |
| 338 | │ │ |
| 339 | │ • Usage analytics │ |
| 340 | │ To improve app performance │ |
| 341 | │ │ |
| 342 | │ • Crash reports │ |
| 343 | │ To fix bugs and issues │ |
| 344 | │ │ |
| 345 | │ • Photos you upload │ |
| 346 | │ Stored securely on our servers │ |
| 347 | │ │ |
| 348 | │ [View Privacy Policy] │ |
| 349 | │ │ |
| 350 | └─────────────────────────────────────────┘ |
| 351 | |
| 352 | |
| 353 | ### Data Deletion Options |
| 354 | |
| 355 | Provide clear data management: |
| 356 | |
| 357 | |
| 358 | ┌─────────────────────────────────────────┐ |
| 359 | │ Your Data │ |
| 360 | ├─────────────────────────────────────────┤ |
| 361 | │ Download My Data ▶ │ |
| 362 | │ Delete My Account ▶ │ |
| 363 | └─────────────────────────────────────────┘ |
| 364 | |
| 365 | |
| 366 | |
| 367 | |
| 368 | ## App Privacy Labels |
| 369 | |
| 370 | ### Required Categories |
| 371 | |
| 372 | Your App Store listing must declare: |
| 373 | |
| 374 | **Data Used to Track You** |
| 375 | Data used for advertising across apps |
| 376 | |
| 377 | **Data Linked to You** |
| 378 | Identifiable data (name, email, etc.) |
| 379 | |
| 380 | **Data Not Linked to You** |
| 381 | Anonymous analytics, crash data |
| 382 | |
| 383 | ### Best Practices |
| 384 | |
| 385 | Be accurate—Apple verifies |
| 386 | Minimize collection to reduce label size |
| 387 | Simpler labels build trust |
| 388 | Update when collection changes |
| 389 | |
| 390 | |
| 391 | |
| 392 | ## Usage String Best Practices |
| 393 | |
| 394 | ### Structure |
| 395 | |
| 396 | |
| 397 | "[App name] [action] to [user benefit]." |
| 398 | |
| 399 | |
| 400 | ### Examples by Permission |
| 401 | |
| 402 | | Permission | Good Example | |
| 403 | |------------|--------------| |
| 404 | | Camera | "MyApp uses the camera to scan barcodes for quick product lookup." | |
| 405 | | Photos | "MyApp saves photos you create to your photo library." | |
| 406 | | Location | "MyApp uses your location to show nearby restaurants and estimated delivery times." | |
| 407 | | Microphone | "MyApp uses the microphone to record voice notes for your journal entries." | |
| 408 | | Contacts | "MyApp accesses contacts to help you split bills with friends." | |
| 409 | |
| 410 | ### What to Avoid |
| 411 | |
| 412 | Generic explanations ("to improve your experience") |
| 413 | Technical jargon |
| 414 | Mentioning advertising without explaining value |
| 415 | Being vague about data use |
| 416 | |
| 417 | |
| 418 | |
| 419 | ## Testing Permissions |
| 420 | |
| 421 | ### Reset Permissions |
| 422 | |
| 423 | |
| 424 | # Reset all permissions for specific app |
| 425 | xcrun simctl privacy booted reset all com.yourapp.bundleid |
| 426 | |
| 427 | # Reset specific permission |
| 428 | xcrun simctl privacy booted reset camera com.yourapp.bundleid |
| 429 | |
| 430 | |
| 431 | ### Test All States |
| 432 | |
| 433 | For each permission: |
| 434 | Never requested (first launch) |
| 435 | Authorized |
| 436 | Denied |
| 437 | Restricted (parental controls) |
| 438 | Limited (Photos) |
| 439 | Provisional (Notifications) |
| 440 | |
| 441 | ### Automated Testing |
| 442 | |
| 443 | |
| 444 | func testCameraPermissionDenied() { |
| 445 | // Set up mock authorization status |
| 446 | mockCameraAuthorization = .denied |
| 447 | |
| 448 | // Trigger camera feature |
| 449 | app.buttons["Take Photo"].tap() |
| 450 | |
| 451 | // Verify fallback UI appears |
| 452 | XCTAssertTrue(app.staticTexts["Camera access needed"].exists) |
| 453 | XCTAssertTrue(app.buttons["Open Settings"].exists) |
| 454 | } |
| 455 | |
| 456 |
Details
- Updated
- Sep 10, 2026
- License
- MIT
- Stars on the repo
- ★ 2,235
- Checked
- Source
- wondelai/skills ↗
- Creator
- wondelai · 319 listings
Similar skills
See all →
SkillCross-Platform Reverse Engineer
SkillBrainstorm Ideas
SkillCode Review Feedback Analyzer
SkillBug Diagnosis Framework
SkillImplement Spec
SkillPR Description WriterMore from wondelai
SkillSoftware Design Philosophy
SkillActionable Metrics Guide
SkillAdvertising Media
SkillAffordance Design
SkillLean UX in Agile Teams
SkillAMP Framework Practitioner