AI-Generated iOS Code: App Store Checklist for AI-Written Code
AI-generated iOS code that builds may still be rejected: check usage descriptions, privacy manifest, APIs above your deployment target and private APIs.
AI-generated iOS code can compile and run and still fail App Review. The trouble usually sits in the parts AI doesn’t handle on its own, such as usage descriptions, the privacy manifest, APIs newer than your deployment target, invented or private methods, user data in logs, and the App Review rules for in-app purchase, sign-in and account deletion. The fix is to run a Release build through the App Store checklist below, grouped by category, with how to check each item and where to look, plus a prompt that asks the AI to check its own work first. The rules follow Apple’s documentation and the App Review Guidelines as of October 2026.
How do you use this App Store checklist?
- Check the Release build. Debug builds contain debug switches, test endpoints and preview code, so results from them don’t count.
- Go through the summary table in order and tick each item. For anything you can’t tick, jump to the matching section below for details.
- Check again every time the AI finishes a large feature, rather than saving it all for submission. New permissions, SDKs and network requests are the easiest to miss.
Apple decides the review outcome. This checklist only helps you rule out common problems you can catch ahead of time.
Summary table (printable)
| Category | Item | How to check | Details |
|---|---|---|---|
| Privacy and permissions | Every permission you use has a usage description that names the specific use | Search for calls such as AVCaptureDevice, PHPhotoLibrary and CLLocationManager, and match each against the NS…UsageDescription keys in Info.plist |
Usage descriptions |
| Privacy and permissions | The app still works for other features after the user denies a permission | On a device, tap Don’t Allow on every permission prompt and walk through the main flows | Rejection reasons |
| Privacy and permissions | Every required reason API is declared in the privacy manifest | Search for UserDefaults, creationDate, systemUptime and similar, and compare with PrivacyInfo.xcprivacy |
Privacy manifest |
| Privacy and permissions | ATT is requested before tracking, and NSUserTrackingUsageDescription is present |
Search for advertisingIdentifier and requestTrackingAuthorization, and check ad and attribution SDKs |
Rejection reasons |
| Privacy and permissions | Users are told and asked for consent before their data goes to a third-party AI service | Find the code that calls AI APIs and check for a disclosure and consent step before sending | Rejection reasons |
| Third-party SDKs | Every SDK on Apple’s list includes a privacy manifest, and binary SDKs are signed | Unzip the .ipa, list PrivacyInfo.xcprivacy files and .framework folders, and compare with the official list |
Privacy manifest |
| Third-party SDKs | Libraries the AI recommends exist and are still maintained | Check recent releases and issues in the library’s official repository | Below |
| APIs | No APIs newer than the deployment target | Build with the minimum deployment target and look for is only available in iOS xx or newer |
SwiftUI with AI |
| APIs | Deprecation warnings are dealt with | Search the build log for deprecated |
Below |
| APIs | No invented methods and no private APIs | has no member build errors, and searches for NSClassFromString, performSelector and valueForKey:@"_ |
Below |
| Data | The new version reads data left by the old version | Install the old version, create data, then install the new version over it | SwiftData |
| Data | Tokens and passwords are in the Keychain, not UserDefaults or files | Search for UserDefaults and write(to: near token and password |
Below |
| Data | Logs don’t print phone numbers, tokens or other private data | Search for print(, NSLog( and privacy: .public |
Device logs |
| Network | ATS isn’t turned off globally, with exceptions only for domains that need them | Search Info.plist for NSAllowsArbitraryLoads |
Network debugging |
| Network | No hard-coded keys or test endpoints in the source | Search for sk_, secret, apiKey, http://, 192.168. and localhost |
Below |
| App Review | Digital content and memberships use in-app purchase (3.1.1) | List every paid entry point and decide which category each falls into | Rejection reasons, StoreKit 2 |
| App Review | An equivalent sign-in option is offered alongside third-party sign-in (4.8) | Count the sign-in options on the sign-in screen | Sign in with Apple |
| App Review | If users can create an account, they can delete it in the app (5.1.1(v)) | Find the delete option in the app and go through the deletion flow | Rejection reasons |
| App Review | No placeholder text, empty screens or endpoints pointing at test environments (2.1) | Search the project for TODO, Lorem, test and staging |
Rejection reasons |
| Build | Built with the Release configuration and a distribution certificate | Check the build command and signing information | App Store on Windows |
| Build | Version and build numbers are higher than the last upload | Unzip the .ipa and check both numbers in Info.plist | Version and build numbers |
| Build | Preview code and debug switches aren’t in the Release build | Check what #Preview and #if DEBUG cover |
SwiftUI with AI |
| Build | Export compliance for encryption is declared | ITSAppUsesNonExemptEncryption is in Info.plist |
Below |
| Build | Capabilities such as push match between entitlements and the provisioning profile | Open embedded.mobileprovision in the package and check its entitlements |
p8 or p12, Bundle ID and profiles |
What do you need before you start checking?
Every section below uses these two things.
Release build. Run KXBuild: Build for App Store (.ipa) in VS Code, or build on the command line, to get build\MyApp.ipa.
1kxbuild build .\MyApp.xcodeproj -c Release --export app-store
An .ipa is a zip archive. Extract it to a temporary folder with the built-in tar. You can double-click the extracted Payload\MyApp.app\Info.plist, PrivacyInfo.xcprivacy and embedded.mobileprovision in VS Code to view them in the Property List Editor, which also reads binary plists.
1mkdir .\ipa-check
2tar -xf .\build\MyApp.ipa -C .\ipa-check
Source search. Search only your own source folders and skip Pods, .build and node_modules. This is the PowerShell form. The sections below give only the keywords.
1Get-ChildItem .\MyApp -Recurse -Include *.swift,*.m,*.mm |
2 Select-String -Pattern 'keyword1|keyword2' |
3 Select-Object Path, LineNumber, Line
On Linux or WSL, use grep.
1grep -rnE --include='*.swift' --include='*.m' --include='*.mm' 'keyword1|keyword2' MyApp
You can also use VS Code’s global search with regular expressions turned on and Pods,.build,node_modules in files to exclude.
What does AI-generated code miss in privacy and permissions?
Usage descriptions. When AI adds code that accesses the camera, photo library, location, microphone or contacts, it often doesn’t update Info.plist. Without the usage description, the system terminates the app when it accesses the feature, and the upload gets ITMS-90683. Search for the keywords below. For each category that matches, find the matching NS…UsageDescription key in Info.plist and make sure the text describes the specific use, such as “Used to scan package tracking numbers”, rather than “This app needs the camera”. For the key names, see Info.plist usage descriptions.
1AVCaptureDevice|PHPhotoLibrary|PHPickerViewController|CLLocationManager|AVAudioSession|CNContactStore|EKEventStore|CBCentralManager|HKHealthStore
PHPickerViewController doesn’t need photo library permission, so a match on it means you may not need full library access. Guideline 5.1.1(iii) asks you to request only the data your core features need, so use the system picker when you can instead of requesting full access.
Required reason APIs. Nearly every app uses UserDefaults, so nearly every app needs PrivacyInfo.xcprivacy. For keywords and reason codes, see how to write a privacy manifest. According to Apple, since May 1, 2024, App Store Connect doesn’t accept apps that use these APIs without declaring a reason.
1UserDefaults|@AppStorage|creationDate|modificationDate|systemUptime|mach_absolute_time|volumeAvailableCapacity|activeInputModes
ATT. Search for advertisingIdentifier and requestTrackingAuthorization. If your code or an ad or attribution SDK tracks users, call requestTrackingAuthorization first and add NSUserTrackingUsageDescription to Info.plist. The App Privacy questionnaire in App Store Connect must match what the app actually does.
Data sent to AI services. Guideline 5.1.2(i) requires you to clearly disclose and get consent before sharing personal data with third parties, including third-party AI. If the app has features such as sending chat content or photos to a large language model, make sure there’s a disclosure and consent step before sending, and mention it in your privacy policy.
What should you check in SDKs the AI recommends?
Apple maintains a list of commonly used third-party SDKs. When a new app includes a listed SDK, or an update adds one, the SDK’s privacy manifest must be included, and SDKs added as binaries must also be signed. The list includes common libraries such as Alamofire, the Firebase family, Kingfisher, SDWebImage, Lottie, hermes and Flutter.
List all frameworks and privacy manifests in the extracted folder and compare them with the list to see what’s missing.
1Get-ChildItem .\ipa-check -Recurse -Filter PrivacyInfo.xcprivacy | Select-Object FullName
2Get-ChildItem .\ipa-check\Payload\MyApp.app\Frameworks -Directory | Select-Object Name
AI library recommendations have two common problems. It suggests an old version that hasn’t been updated in years and has no privacy manifest, or it gives a package name that doesn’t exist. Before adding a dependency, check three things in the library’s official repository. The package name matches the repository, there’s been a release in the last year, and the latest version includes PrivacyInfo.xcprivacy.
How do you find APIs above your deployment target and private APIs?
Deployment target and deprecated APIs
AI writes code for the latest iOS by default. When the project’s minimum deployment target is lower than an API requires, the build fails. Here’s the actual output from building a piece of AI-generated code with iOS 16 as the deployment target.
1error: 'ContentUnavailableView' is only available in iOS 17.0 or newer
2warning: 'setStatusBarHidden(_:with:)' was deprecated in iOS 9.0: Use -[UIViewController prefersStatusBarHidden]
3error: value of type 'UIView' has no member 'applyBlurEffectAutomatically'
The three lines are three kinds of problems. Wrap the first kind in if #available(iOS 17, *) and write a fallback UI for older versions, as shown in common pitfalls when writing SwiftUI with AI. For the second kind, switch to the newer API the warning suggests. Deprecated code compiles today, but a future SDK may remove it. The third kind is a method the AI invented, covered in the next section.
Check the project’s deployment target first, then read the build log. On the command line you can dump the build settings.
1kxbuild project settings dump .\MyApp.xcodeproj | findstr IPHONEOS_DEPLOYMENT_TARGET
As of October 2026, Apple’s upload requirements say that since April 28, 2026, uploaded apps must be built with the iOS 26 SDK, and since September 9, 2026, iOS apps must have a deployment target of iOS 13 or later. For choosing a deployment target, see how to choose a minimum iOS version.
Invented methods and private APIs
AI sometimes writes methods that look reasonable but don’t exist in the SDK. In Swift these calls don’t compile and report has no member, which is the most reliable check. When you hit this error, don’t ask the AI to “write it another way so it compiles”. First search the Apple Developer Documentation by class name to see whether a public API does what you need.
More dangerous is code that does compile. For methods called dynamically by string, the compiler doesn’t check whether they exist or are public, and guideline 2.5.1 requires public APIs only. Search for these keywords and confirm every name being called can be found in the official documentation.
1NSClassFromString|NSSelectorFromString|performSelector|value\(forKey: *"_|valueForKey:@"_|setValue\(.*forKey: *"_|dlopen|dlsym
Key and class names starting with an underscore are almost always private. Rejection messages usually name the specific symbols, so search your code and dependencies for those names. See 12 common App Store rejection reasons.
What should you watch for in data storage and logs?
Compatibility with old data. When AI changes a data model, it only sees the current code and doesn’t know that users’ phones still hold data written by older versions. If you add a non-optional field to a Codable struct, old JSON without that key fails to decode, and the user’s settings or cache are lost. Give new fields a default with decodeIfPresent.
1struct UserSettings: Codable {
2 var fontSize: Int
3 var darkMode: Bool
4 var language: String
5
6 enum CodingKeys: String, CodingKey { case fontSize, darkMode, language }
7
8 init(from decoder: Decoder) throws {
9 let c = try decoder.container(keyedBy: CodingKeys.self)
10 fontSize = try c.decode(Int.self, forKey: .fontSize)
11 darkMode = try c.decode(Bool.self, forKey: .darkMode)
12 language = try c.decodeIfPresent(String.self, forKey: .language) ?? "en"
13 }
14}
To check, install the previous release on a device, use it for a while to create data, then install the new version over it without deleting the app first. Also watch for UserDefaults key names the AI may have changed. If you use SwiftData, write a migration when the model changes. See Persist data locally with SwiftData.
Keep sensitive data in the Keychain. AI likes to write sign-in tokens to UserDefaults because it’s the shortest code. UserDefaults is a plain-text plist file, so tokens, passwords and keys belong in the Keychain.
1import Foundation
2import Security
3
4enum TokenStore {
5 private static let service = "com.example.myapp.auth"
6 private static let account = "accessToken"
7
8 static func save(_ token: String) -> Bool {
9 let base: [String: Any] = [
10 kSecClass as String: kSecClassGenericPassword,
11 kSecAttrService as String: service,
12 kSecAttrAccount as String: account,
13 ]
14 SecItemDelete(base as CFDictionary)
15 var item = base
16 item[kSecValueData as String] = Data(token.utf8)
17 item[kSecAttrAccessible as String] = kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
18 return SecItemAdd(item as CFDictionary, nil) == errSecSuccess
19 }
20
21 static func load() -> String? {
22 let query: [String: Any] = [
23 kSecClass as String: kSecClassGenericPassword,
24 kSecAttrService as String: service,
25 kSecAttrAccount as String: account,
26 kSecReturnData as String: true,
27 kSecMatchLimit as String: kSecMatchLimitOne,
28 ]
29 var result: AnyObject?
30 guard SecItemCopyMatching(query as CFDictionary, &result) == errSecSuccess,
31 let data = result as? Data else { return nil }
32 return String(decoding: data, as: UTF8.self)
33 }
34}
kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly means the item is readable after the first unlock following a restart and never migrates to another device. Search for UserDefaults, @AppStorage and write(to: near token|password|secret, and switch any matches to the code above.
Keep private data out of logs. While debugging, AI adds print everywhere and prints request parameters, phone numbers and tokens as is. Before submission, search for print( and NSLog(, remove debug output, and move logs you want to keep to Logger. Strings interpolated into Logger are redacted by default. Add privacy: .public only to values you’ve confirmed aren’t sensitive.
1import os
2
3private let authLog = Logger(subsystem: "com.example.myapp", category: "auth")
4
5func logLogin(userID: String, phone: String, success: Bool) {
6 authLog.notice("Login result \(success) user \(userID, privacy: .private(mask: .hash))")
7 authLog.debug("Phone number \(phone, privacy: .private)")
8}
Search for privacy: .public too and check that it doesn’t follow an account name, phone number, token or similar value. For Logger levels and privacy options, see Read iPhone device logs with Logger and os_log.
How do you check network settings and hard-coded secrets?
ATS. When an http request fails, the fix AI suggests most often is adding NSAllowsArbitraryLoads = YES to Info.plist, which removes ATS protection from the whole app. According to Apple’s documentation, you have to justify this kind of exception during App Review, and it may trigger additional review. Open the extracted Info.plist and look under NSAppTransportSecurity. If you can move the server to https, delete the exception. If only a few domains can’t change, open NSExceptionDomains for them only. See Debug iOS network requests for how.
Hard-coded keys and endpoints. AI-generated samples often put the API key right in the code. Anyone can read strings compiled into the app from the package, so private keys for third-party services belong on your server, and the app calls only your own API. Search the source and the extracted app for these keywords.
1sk_|secret|apiKey|api_key|Bearer |PRIVATE KEY|http://|192\.168\.|localhost|staging|test\.
You can also search the executable in the extracted folder directly to confirm no strings are left.
1grep -a -o -E 'sk_[A-Za-z0-9]{8,}|192\.168\.[0-9.]+|http://[A-Za-z0-9.-]+' ipa-check/Payload/MyApp.app/MyApp | sort -u
Which App Review rules does AI ignore?
AI rarely considers these three rules on its own, so check them yourself. The full text is in the App Review Guidelines. For reading rejection messages and replying, see 12 common App Store rejection reasons.
| Guideline | Requirement | How to check |
|---|---|---|
| 3.1.1 In-app purchase | Unlocking features, memberships, virtual currency or paid content in the app must use in-app purchase, not your own unlock mechanism such as unlock codes | List every paid entry point. Digital content goes through StoreKit. Physical goods and services used outside the app may use other payment methods under 3.1.3(e) |
| 4.8 Login services | Apps that use a third-party or social login as the primary account must also offer a login option that collects only name and email, lets users hide their email, and doesn’t collect interactions for advertising | If the sign-in screen offers only logins such as Google, add Sign in with Apple |
| 5.1.1(v) Account deletion | Apps that support account creation must offer account deletion in the app, and features not tied to an account must work without signing in | Find the delete option in settings and confirm it deletes the account and its data, not just disables it |
When AI writes payment features, it often plugs in web payments or a third-party payment SDK directly. For digital content, that triggers 3.1.1. For the full in-app purchase steps, see Add in-app purchases with StoreKit 2. For Sign in with Apple, see Add Sign in with Apple, which also covers revoking tokens when an account is deleted.
AI-generated screens often contain placeholder content. Search the project for TODO|FIXME|Lorem|placeholder|sample data. Guideline 2.1 requires that you submit the final version.
What should you confirm in the final build?
- Release and distribution certificate. Build the App Store package with the Release configuration, an Apple Distribution certificate and an App Store provisioning profile. See Publish to the App Store on Windows.
- Version and build numbers. Open the extracted Info.plist and check
CFBundleShortVersionStringandCFBundleVersion. The build number must be higher than the last upload. See iOS version and build numbers for the rules. - Debug code. Search for
#if DEBUGand confirm that test endpoints, debug menus and sign-in shortcuts are all inside it. Production code shouldn’t reference sample data from#Preview. See common pitfalls when writing SwiftUI with AI. - Export compliance. Apps that only use HTTPS and system encryption usually set ITSAppUsesNonExemptEncryption to NO in Info.plist. According to Apple’s documentation, without this key App Store Connect asks the export compliance questions for every new upload. Whether you qualify for the exemption depends on the encryption you actually use.
- Capabilities and the profile. Double-click the extracted
embedded.mobileprovisionand check that its entitlements include the capabilities you use, such asaps-environmentfor push. If the AI added push registration code but the capability isn’t turned on, the upload gets ITMS-90078. For push credentials, see p8 or p12 for push notifications. For upload error codes, see the ITMS error code reference.
How do you get the AI to check its own work first?
Give the prompt below to the AI along with the relevant code, Info.plist and PrivacyInfo.xcprivacy. Have it list problems against the checklist first, then verify each one yourself. The AI’s answer is only a lead, so confirm every item with the build and search methods above.
1You are an iOS App Store submission reviewer. Check the code and configuration files I provide. Only list problems, don't change the code.
2
3Project information:
4- Minimum deployment target: iOS 16.0
5- Language and UI: Swift 6, SwiftUI
6- Account sign-up: yes; sign-in methods: phone number, Google
7- Paid content: membership subscription
8
9Check each item and output "file:line | problem | basis | suggestion":
101. For every call that accesses the camera, photo library, location, microphone or contacts, is there a matching NS…UsageDescription in Info.plist, and does the text describe the specific use?
112. For any UserDefaults, file timestamp, system boot time, disk space or active keyboard APIs used, does PrivacyInfo.xcprivacy declare the matching category?
123. Are any APIs newer than iOS 16.0 used without an #available check?
134. Are there methods you aren't sure exist in the Apple SDK, or methods called dynamically by string (NSClassFromString, performSelector, keys starting with an underscore)? List them, mark them "needs manual verification", and don't assume they exist.
145. Are tokens or passwords stored in UserDefaults or files? Do print, NSLog or Logger output phone numbers, tokens or other personal information?
156. Does Info.plist contain NSAllowsArbitraryLoads? Does the code contain hard-coded keys, http URLs or private network IPs?
167. Against App Review Guidelines 3.1.1, 4.8 and 5.1.1(v): do the payment method, sign-in methods and in-app account deletion comply?
178. Are there TODOs, placeholder text or test environment URLs?
18
19For items with no problems, write "none found". For anything uncertain, write "uncertain". Don't invent API names or guideline sections.
Replace the project information lines with your own. Before letting the AI change any code, resolve the items it marked “needs manual verification”, then run your AI-generated iOS code through the checklist above once more.
FAQ
What should I check first in AI-written iOS code before submission?
Check four things first. Every permission has a specific usage description, PrivacyInfo.xcprivacy declares the required reason APIs you use, the build has no availability errors at the minimum deployment target, and payments, sign-in and account deletion follow guidelines 3.1.1, 4.8 and 5.1.1(v). These are the problems AI is least likely to handle on its own.
How do I tell whether a method the AI wrote really exists?
Build first. In Swift, a method that doesn’t exist fails with has no member. For code that compiles but calls by string (NSClassFromString, performSelector, keys starting with an underscore), search each name in the Apple Developer Documentation, and treat anything you can’t find as a private API and remove it.
Can I submit an app where the AI set NSAllowsArbitraryLoads to YES?
According to Apple’s documentation, you have to justify this exception during App Review, and it may trigger additional review. The safer route is to move the server to https and remove it. If only a few domains can’t change, configure NSExceptionDomains for those domains only.
Can I store sign-in tokens in UserDefaults?
It isn’t recommended. UserDefaults is a plain-text plist file, so sensitive values such as tokens and passwords belong in the Keychain, with an accessibility level such as kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly as needed.
Do I still need to check by hand after the AI checks itself?
Yes. Treat the AI’s self-check only as leads. It can miss problems and can invent rules that don’t exist. Verify every item by building, searching the source, unzipping the .ipa and testing on a device, and rely on Apple’s official text for review rules.