From ios-from-web-guide
MANDATORY for Info.plist work. Use before adding imports that require user permission (PhotosUI, AVFoundation camera, CoreLocation, ATT, HealthKit, Contacts, EventKit).
How this skill is triggered — by the user, by Claude, or both
Slash command
/ios-from-web-guide:ios-info-plist-privacy-stringsThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
1. **Every permission-gated framework import requires a matching `NSxxxUsageDescription` string in `Info.plist`.** iOS kills the app on first-use of the API if the string is missing — no prompt, no log, just a hard crash with `TCC_CRASHING_DUE_TO_PRIVACY_VIOLATION`.
NSxxxUsageDescription string in Info.plist. iOS kills the app on first-use of the API if the string is missing — no prompt, no log, just a hard crash with TCC_CRASHING_DUE_TO_PRIVACY_VIOLATION.ITSAppUsesNonExemptEncryption = false is always set for apps that use only HTTPS via URLSession (99% of apps). This pre-answers the App Store Connect export-compliance nag that otherwise appears on every build upload.validate_pre_archive.sh before archive. Fix them there, not after TestFlight rejects.Info.plist in the same edit. Don't wait until crash.import PhotosUI, import AVFoundation, import CoreLocation, import AppTrackingTransparency, import Contacts, import EventKit, or import HealthKit to a Swift file.Info.plist directly.| Import | Required Info.plist key(s) |
|---|---|
PhotosUI, Photos | NSPhotoLibraryUsageDescription, plus NSPhotoLibraryAddUsageDescription if you only save |
AVFoundation (camera via AVCaptureDevice) | NSCameraUsageDescription |
AVFoundation (mic via AVAudioSession) | NSMicrophoneUsageDescription |
CoreLocation | NSLocationWhenInUseUsageDescription; NSLocationAlwaysAndWhenInUseUsageDescription if background |
AppTrackingTransparency | NSUserTrackingUsageDescription |
Contacts | NSContactsUsageDescription |
EventKit (calendar) | NSCalendarsUsageDescription |
EventKit (reminders) | NSRemindersUsageDescription |
HealthKit | NSHealthShareUsageDescription and NSHealthUpdateUsageDescription |
UserNotifications | No Info.plist key — request permission via UNUserNotificationCenter at runtime |
LocalAuthentication (Face ID) | NSFaceIDUsageDescription |
Speech | NSSpeechRecognitionUsageDescription |
If you don't see your import here, search Apple's Information Property List reference before guessing.
Use the app's product name and the specific feature that triggers the prompt. iOS shows the string verbatim, so every character counts.
NSPhotoLibraryUsageDescription:
"{AppName} needs photo access to attach images to your {noun}."
e.g. "Trays needs photo access to attach images to your recipes."
NSCameraUsageDescription:
"{AppName} uses the camera to take photos of your {noun}."
NSMicrophoneUsageDescription:
"{AppName} uses the microphone to record {noun}."
NSLocationWhenInUseUsageDescription:
"{AppName} uses your location to show {noun} near you."
NSUserTrackingUsageDescription:
"Allow tracking so we can show you more relevant {noun}."
(ATT copy should be plain — it's also shown in the system prompt.)
NSContactsUsageDescription:
"{AppName} uses your contacts to find friends already on the app."
NSCalendarsUsageDescription:
"{AppName} adds your {noun} to the calendar when you opt in."
NSFaceIDUsageDescription:
"{AppName} uses Face ID to unlock your account."
NSHealthShareUsageDescription:
"{AppName} reads {metric} from Health to {benefit}."
NSHealthUpdateUsageDescription:
"{AppName} saves {metric} to Health when you log {activity}."
Bad copy (will be rejected):
Good copy:
Info.plist snippet<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
</array>
<key>NSPhotoLibraryUsageDescription</key>
<string>Trays needs photo access to attach images to your recipes.</string>
<key>NSCameraUsageDescription</key>
<string>Trays uses the camera to take photos of your recipes.</string>
If you use project.yml (recommended), these live under targets.<app>.info.properties instead of a freestanding Info.plist.
ITSAppUsesNonExemptEncryptionSet this key once and forget it. HTTPS via URLSession is exempt per Apple's export-compliance rules, and the overwhelming majority of apps qualify as "uses only standard encryption."
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
Without this key, App Store Connect shows a "Missing Compliance" banner on every build and blocks TestFlight distribution until you answer the questionnaire manually.
Only set to true (and file annual self-classification) if you ship custom cryptography — rare.
When a skill-aware edit adds, e.g., import PhotosUI to a Swift file, the corresponding NSPhotoLibraryUsageDescription should be added to Info.plist in the same turn. The workflow:
import[[:space:]]+PhotosUI).Info.plist for the matching key.The validator (see below) will catch anything missed.
validate_pre_archive.sh enforces presencescripts/validate_pre_archive.sh runs as part of the pre-archive hook. Check 4 walks the following pairs:
import[[:space:]]+PhotosUI → NSPhotoLibraryUsageDescription
AVCaptureDevice → NSCameraUsageDescription
import[[:space:]]+CoreLocation → NSLocationWhenInUseUsageDescription
import[[:space:]]+AppTrackingTransparency → NSUserTrackingUsageDescription
If any .swift file matches the import regex and the key is declared in neither the project's Info.plist (found anywhere in the tree) nor project.yml's info.properties, validation fails with exit code 2 and prints the key name to add. To bypass in rare legitimate cases:
IOS_FROM_WEB_SKIP_VALIDATOR=1 xcodebuild archive ...
Document the reason in the commit message when you do.
Cause: missing NSCameraUsageDescription. iOS kills the process synchronously with a TCC violation that doesn't always surface in Xcode's console.
Fix: add the key. Run validate_pre_archive.sh locally to catch it pre-flight.
Cause: ITSAppUsesNonExemptEncryption absent.
Fix: set <false/>. Re-archive and re-upload.
Cause: The key is in a Info.plist that's not the one bundled into the app — e.g., you edited YourApp/Info.plist but the target is configured to use Info-Release.plist. With XcodeGen, the info.path in project.yml is the source of truth.
Fix: confirm which plist the target uses (xcodebuild -showBuildSettings | grep INFOPLIST_FILE).
Cause: added NSLocationAlwaysAndWhenInUseUsageDescription but are calling requestWhenInUseAuthorization() — iOS shows the WhenInUse string for that API. You need both strings if you ever upgrade to Always.
Fix: add both keys with distinct copy. WhenInUse is always required as the prerequisite.
There is no standalone privacy-strings template — the canonical project.yml.template under <plugin-root>/templates/project.yml.template already includes ITSAppUsesNonExemptEncryption: false and placeholder NSPhotoLibraryUsageDescription / NSCameraUsageDescription entries. Fill in per-app copy as you add the relevant imports.
ios-project-structure — where Info.plist and project.yml live in the opinionated layout.ios-app-icon-asset-prep — the other common "blocks archive" gotcha.npx claudepluginhub j-morgan6/ios-from-web-guide --plugin ios-from-web-guideGuides collaborative design exploration before implementation: explores context, asks clarifying questions, proposes approaches, and writes a design doc for user approval.
Creates structured, bite-sized implementation plans from specs or requirements before writing code. Useful for breaking down multi-step tasks into testable steps with file structure and task boundaries.
Resolves in-progress git merge or rebase conflicts by analyzing history, understanding intent, and preserving both changes where possible. Runs automated checks after resolution.