App Crashes on Launch From Missing NSXxxUsageDescription

iOS terminates the app the moment a privacy-protected API is called without a matching Info.plist usage description. Diagnose, add the right key, and ship.

You tap the Scan Receipt button, the app freezes for a frame, and disappears. No crash dialog, no error toast — just the home screen. In the device console you find a line like “This app has crashed because it attempted to access privacy-sensitive data without a usage description. The app’s Info.plist must contain an NSCameraUsageDescription key with a string value explaining to the user how the app uses this data.” iOS does not throw a recoverable exception here; it terminates the process immediately with SIGABRT. The fix is one Info.plist string, but figuring out which key (there are 30+) and writing a description that survives App Review takes a methodical pass.

Common causes

Listed by which API surface most often catches teams.

1. Camera / microphone / photo library access without the right key

Calling AVCaptureDevice.requestAccess(for: .video), PHPhotoLibrary.requestAuthorization, or instantiating UIImagePickerController with .camera source type — without NSCameraUsageDescription, NSMicrophoneUsageDescription, or NSPhotoLibraryUsageDescription — terminates the process.

How to spot it: Crash log shows TCC (Transparency, Consent, Control) framework in the trace; console line names the missing key explicitly.

2. Location used without NSLocationWhenInUseUsageDescription

CLLocationManager.requestWhenInUseAuthorization() requires NSLocationWhenInUseUsageDescription. If you also call requestAlwaysAuthorization(), you need both NSLocationWhenInUseUsageDescription and NSLocationAlwaysAndWhenInUseUsageDescription since iOS 11.

How to spot it: Console: “Trying to start MapKit location updates without prompting for location authorization.” or app dies on the first locationManager.startUpdatingLocation().

3. Tracking via IDFA without NSUserTrackingUsageDescription (iOS 14+)

ATTrackingManager.requestTrackingAuthorization requires NSUserTrackingUsageDescription. Without it, the call hangs or returns .denied silently; in some SDKs (Facebook, Branch) it triggers a hard crash during init.

How to spot it: First-launch crash when AppDelegate calls Settings.shared.advertiserTrackingEnabled = true or similar SDK init code.

4. Bluetooth without NSBluetoothAlwaysUsageDescription

Instantiating CBCentralManager or CBPeripheralManager requires NSBluetoothAlwaysUsageDescription (iOS 13+). The old NSBluetoothPeripheralUsageDescription is deprecated; setting only the old key still crashes on iOS 13+.

How to spot it: Crash log mentions CoreBluetooth and the key name in the console.

5. Local network access on iOS 14+

Any non-trivial socket call (NWConnection, multipeer connectivity, Bonjour browsing) requires NSLocalNetworkUsageDescription. Apps that worked fine on iOS 13 started crashing or silently dropping connections on iOS 14.

How to spot it: Network feature works on iOS 13 simulator but on iOS 14+ device shows “App would like to find and connect to devices on your local network” alert; if Info.plist key missing, alert never shows and connection silently times out.

6. Contacts / Calendar / Reminders / Health / HomeKit / Motion

Each protected resource has its own key: NSContactsUsageDescription, NSCalendarsUsageDescription, NSRemindersUsageDescription, NSHealthShareUsageDescription, NSHealthUpdateUsageDescription, NSHomeKitUsageDescription, NSMotionUsageDescription. Crash trace always names the missing key in the console.

How to spot it: Console: “This app has crashed because it attempted to access privacy-sensitive data without a usage description. The app’s Info.plist must contain an <KEY>…“

7. The key is set in source-controlled Info.plist but stripped from the archive

Most painful: the project compiles, runs on the simulator, but crashes on the TestFlight build. Build settings (Custom Info.plist file path, conditional builds, scripts that regenerate Info.plist) can strip keys at archive time.

How to spot it: Local Debug build works; only Release / Distribution archive crashes. Extracting Info.plist from the .ipa shows the key is missing.

Before you start

  • Reproduce the crash on a device (not just the simulator) — some protected APIs return canned values on the simulator and never crash.
  • Symbolicate the crash log or watch device Console.app live so you can read the explicit error string iOS prints — it names the missing key.
  • Note whether the issue appears only in Release builds; if so, look at build settings, not source.

Information to collect

  • The exact crash line from Console.app — copy the entire “This app has crashed because…” sentence.
  • The output of grep -E "NS[A-Z][A-Za-z]+UsageDescription" Info.plist for every target’s Info.plist.
  • The exact API call that triggered the crash (camera, location, ATT, Bluetooth, etc.).
  • A list of every SDK that initializes at launch — many request permissions in their init.
  • Build settings: INFOPLIST_FILE, INFOPLIST_PREPROCESS, any custom build phases that touch Info.plist.

Step-by-step fix

Step 1: Read the exact missing key from console

Plug the device in, open Console.app on Mac, filter by your app name, reproduce the crash. The line is unambiguous:

This app has crashed because it attempted to access privacy-sensitive data
without a usage description. The app's Info.plist must contain an
NSCameraUsageDescription key with a string value explaining to the user
how the app uses this data.

Treat this as authoritative — it names exactly which key to add.

Step 2: Add the key with a user-facing, specific description

Open the App target’s Info.plist (or INFOPLIST_KEY_* build settings in modern Xcode). Add the key:

<key>NSCameraUsageDescription</key>
<string>Scan receipts and barcodes to add items to your expense list.</string>

Description rules App Review enforces:

  • Must be specific (not “for camera access” — must say what user benefit).
  • Must be in the user’s locale; provide InfoPlist.strings for each supported language.
  • Should not say “may” / “might” — describe actual use.
  • Must match what your app actually does at the moment of the prompt.

Step 3: Verify the key reaches the archive

Build and archive, then extract:

xcrun -sdk iphoneos PackageApplication \
  -v $ARCHIVE_PATH/Products/Applications/YourApp.app
unzip -p YourApp.ipa "Payload/YourApp.app/Info.plist" \
  | plutil -convert xml1 -o - - \
  | grep -A1 UsageDescription

Or simpler — right-click the .xcarchive → Show Package Contents → Products/Applications/YourApp.app → Info.plist, and inspect directly.

Step 4: If your project uses modern Xcode (no Info.plist file)

Xcode 14+ stores Info.plist keys in Build Settings. Add:

INFOPLIST_KEY_NSCameraUsageDescription = Scan receipts and barcodes to add items to your expense list.

Or in the target’s Info tab, click + and add the key. The build system generates a synthetic Info.plist at compile time.

Step 5: Localize the description for every supported language

In Info.plist, the English string is the fallback. For each locale, add InfoPlist.strings:

// en.lproj/InfoPlist.strings
"NSCameraUsageDescription" = "Scan receipts and barcodes to add items to your expense list.";

// zh-Hans.lproj/InfoPlist.strings
"NSCameraUsageDescription" = "扫描小票和条码,将物品添加到你的支出列表。";

// es.lproj/InfoPlist.strings
"NSCameraUsageDescription" = "Escanea recibos y códigos de barras para agregar artículos a tu lista de gastos.";

Without localized strings, App Review can reject for “non-English speakers see English text in a system permission alert.”

Step 6: Audit every SDK for hidden permission requests

SDKs request permissions on init:

nm YourApp.app/YourApp | grep -E "requestAuthorization|requestAccess|requestTracking"

Or check release notes for SDKs you ship. Common offenders: Facebook SDK (ATT), Branch (ATT, location), Firebase Messaging (notifications), Adjust (ATT), audio SDKs (microphone). Every permission an SDK touches needs a usage description in your Info.plist even if your own code never calls that API.

Step 7: Add a missing-key CI check

A 5-line script in your CI pipeline prevents this regression:

REQUIRED_KEYS="NSCameraUsageDescription NSPhotoLibraryUsageDescription NSLocationWhenInUseUsageDescription"
for key in $REQUIRED_KEYS; do
  /usr/libexec/PlistBuddy -c "Print :$key" "$APP_PATH/Info.plist" \
    || { echo "Missing $key"; exit 1; }
done

Run this against the archived .app, not the source Info.plist, so build-setting drift is caught.

Verify

  • Cold-launch the app on a real device, exercise the feature that previously crashed; the system permission alert appears with your description text.
  • Decline the permission; the app handles it gracefully (no crash, shows fallback UI).
  • Re-launch in a different language locale; the description text appears in that locale.
  • Crash log from yesterday’s TestFlight build no longer reproduces on the new build.
  • CI check passes for archived build.

Long-term prevention

  • Maintain a per-target Info.plist checklist of every usage key your code paths require; review on every release.
  • Treat new SDK additions as triggering a usage-description review; the SDK’s docs always list which permissions it touches.
  • Localize every usage description into every shipped language; use a CI step that fails if InfoPlist.strings is missing a key.
  • Wire a runtime check at first launch in DEBUG that asserts each key exists; surface it as a fatal error in dev so you catch missing keys before release.
  • Pin the build’s archived Info.plist as a build artifact; compare across releases to catch silent drops.
  • For complex apps, write integration tests that hit each permission-gated feature on a fresh simulator (no prior consents); they catch missing keys immediately.

Common pitfalls

  • Editing the Info.plist string directly in the system permission alert preview — that is a runtime display, not the source of truth. Change source Info.plist.
  • Using NSLocationAlwaysUsageDescription alone on iOS 11+; iOS requires NSLocationWhenInUseUsageDescription plus NSLocationAlwaysAndWhenInUseUsageDescription for always-authorization apps.
  • Vague descriptions like “We need camera access.” App Review rejects this — be specific.
  • Setting the key in the Watch target but not the iOS App target (or vice versa) when both call the API.
  • Removing the key in a refactor without realizing the SDK that previously triggered it is still bundled — crash returns on next release.
  • Assuming the simulator catches this — simulator returns mock data for many APIs, never triggering the missing-key crash.

FAQ

Q: Console says I need NSCameraUsageDescription, but I do not use the camera. Why?

A bundled SDK does. Run grep -RE "AVCapture|UIImagePicker|requestAccess.*video" Pods/ to find it. You either add the description for that SDK, or remove the SDK if you do not need that feature.

Q: Can I provide an empty string for the description?

No. App Review rejects empty or single-word strings. Apple recommends at least one sentence explaining how the app uses the data.

Q: My Watch app crashed but I added the key to the phone app’s Info.plist.

Watch apps have their own Info.plist. Add the key to the Watch target’s Info.plist as well. Same applies to App Clips, Widgets, and any framework that initializes a CLLocationManager standalone.

Q: Why does the simulator not crash for me?

Simulator’s TCC enforcement is lenient for some APIs; in particular, AVCaptureDevice.requestAccess may auto-return on the simulator without checking Info.plist. Always test on a real device. See TestFlight Build Missing on Device when device testing setup is the issue.

Q: My App Review rejection says “the purpose string is too vague.” What is acceptable?

Reviewer wants you to name the feature: “To scan receipts for expense tracking,” not “For camera access.” Reference the specific in-app feature that uses the data.

Tags: #Troubleshooting #ios #info-plist #privacy #crash