iOS¶
iOS checks: Info.plist, Podfile, SwiftLint, privacy/encryption compliance, SwiftPM dependency reproducibility, and the App Store build-SDK requirement.
35 checks.
ios-allows-arbitrary-loads¶
Severity: Medium
Disabling ATS allows insecure HTTP connections, exposing the app to man-in-the-middle attacks and data interception.
How to fix: Remove NSAllowsArbitraryLoads or set it to false. Add per-domain exceptions only for domains that truly require HTTP.
ios-app-icon-alpha-channel¶
Severity: High
Nothing in the local toolchain validates this: the app builds, runs, and archives, and Xcode reports the upload as successful. The rejection arrives afterwards by email, so the defect is typically discovered on release day with the build already cut. It costs one export to fix and a full release cycle to discover.
How to fix: Re-export the 1024x1024 icon as an opaque PNG (RGB, no alpha channel and no tRNS chunk) — flatten it against the background colour rather than relying on transparency. sips -s format png --setProperty hasAlpha false or an ImageMagick -background white -alpha remove -alpha off pass does it; the smaller idioms in the same set may keep their alpha.
ios-associated-domain-invalid¶
Severity: High
The system ignores an entry it cannot parse, so the associated domain is never claimed: Universal Links open Safari, Handoff does not hand off, shared web credentials do not autofill. Nothing about the build, the signature or the install reports it — the entitlement is present and looks configured, which is why this usually costs a long debugging session that starts on the server side.
How to fix: Write each entry as service:host with no scheme, path or trailing slash — applinks:example.com, webcredentials:example.com, or applinks:*.example.com for a subdomain wildcard. The only supported query is ?mode=developer / ?mode=managed.
ios-ats-exception-weakness¶
Severity: Low
Category-wide arbitrary loads (web/media), per-domain insecure HTTP exceptions, and sub-1.2 TLS minimums each re-open man-in-the-middle exposure for part of the app's traffic — easy to miss because the obvious ATS flag stays false.
How to fix: Remove exceptions the app does not need; serve those domains over HTTPS with TLS 1.2+. Keep NSAllowsArbitraryLoadsInWebContent/ForMedia only if the app genuinely renders arbitrary third-party web/media content.
ios-background-mode-missing-usage¶
Severity: Medium
A background mode that needs a purpose string (location, bluetooth-central/peripheral) crashes at runtime when the API is used and is a documented App Store rejection reason if the key is missing.
How to fix: Add the matching *UsageDescription key (e.g. NSLocationWhenInUseUsageDescription, NSBluetoothAlwaysUsageDescription) to Info.plist with a clear justification — or remove the background mode if the app does not use it.
ios-bitcode-enabled¶
Severity: Low
Bitcode was removed from the App Store pipeline by Apple. Enabling it adds build overhead and can cause unexpected linker behavior without any benefit.
How to fix: Set ENABLE_BITCODE = NO in your Xcode project build settings or xcconfig files. This is the default in Xcode 14+.
ios-build-sdk-requirement¶
Severity: Info
Apple raises the required build SDK roughly yearly. A stale CI Xcode image silently blocks every App Store upload once the deadline passes.
How to fix: Pin your CI to Xcode 26+ (e.g. the macOS runner image / xcode-select) and keep local Xcode current.
ios-capability-missing-usage¶
Severity: Medium
A capability that always needs a purpose string (HealthKit, HomeKit) crashes at runtime the moment its API is used without the string, and is a documented App Store rejection reason.
How to fix: Add the matching NS*UsageDescription key (e.g. NSHealthShareUsageDescription, NSHomeKitUsageDescription) to the app-target Info.plist with a clear justification — or disable the capability in the .entitlements file if it is unused.
ios-dead-code-stripping-disabled¶
Severity: Medium
This is sometimes disabled on purpose — a plugin system or other runtime code registration can depend on code the linker would otherwise consider unreachable — so confirm the disable is deliberate before flipping it. If it is not, the Release binary carries unreferenced code with no runtime benefit, and the build stays green either way.
How to fix: Set DEAD_CODE_STRIPPING = YES for the Release configuration (the Xcode default) unless something depends on the retained code being reachable at runtime.
ios-dead-code-stripping-unresolved¶
Severity: Info
Silence about a build setting means "not read", not "correctly configured". This is the shape a Flutter or CocoaPods project normally has — the committed .xcconfig includes a generated file or a Pods target-support file, neither of which is in the repository — so a scan that said nothing here would look identical to a scan of a project that sets everything correctly.
How to fix: Run xcodebuild -showBuildSettings -project <project> -scheme <scheme> -configuration Release (after whatever generates the missing file) and read DEAD_CODE_STRIPPING from its output. Committing the resolved value in the project file or in a tracked .xcconfig also makes it readable here.
ios-deployment-target-drift¶
Severity: Medium
When the Podfile/SPM floor and the Xcode deployment target diverge, pods/packages compile against a different minimum than the app links for — you get "building for iOS X but linked for iOS Y" warnings, and code guarded on the higher floor can crash on the lower one.
How to fix: Align the minimum iOS version across the Xcode project (IPHONEOS_DEPLOYMENT_TARGET), the Podfile (platform :ios) and any SwiftPM platforms declaration.
ios-extension-activation-truepredicate¶
Severity: Medium
TRUEPREDICATE makes the extension show up in the share sheet for content it cannot handle — poor UX and a common App Store review rejection. A specific NSExtensionActivationRule dict scopes it to the types the extension actually supports.
How to fix: Replace TRUEPREDICATE with an NSExtensionActivationRule dict declaring the supported types (e.g. NSExtensionActivationSupportsWebURLWithMaxCount, NSExtensionActivationSupportsImageWithMaxCount).
ios-hardcoded-paths¶
Severity: Medium
Hardcoded paths break builds on other developers' machines and CI environments, causing mysterious build failures.
How to fix: Replace absolute paths with relative paths or Xcode build setting variables like $(SRCROOT).
ios-info-plist-overview¶
Severity: Info
Understanding the Info.plist configuration helps identify app metadata and security settings.
How to fix: Review Info.plist settings to ensure they match your release requirements.
ios-ldflags-inherited-dropped¶
Severity: Medium
Nothing reports the loss: the build stays green, and the setting reads as correct in Xcode because the file shows exactly what was written. The expensive case is OTHER_LDFLAGS losing -ObjC — Objective-C categories inside static libraries then never get linked in, and the app fails with "unrecognized selector sent to instance" in release, on the one screen that uses them. A dropped HEADER_SEARCH_PATHS breaks the compile for whoever builds a target this one used to feed, and a dropped SWIFT_ACTIVE_COMPILATION_CONDITIONS silently compiles out the code behind a build flag.
How to fix: Start the value with $(inherited) — OTHER_LDFLAGS = $(inherited) -lz — so the assignment extends the inherited list instead of replacing it. Drop it deliberately only when the included value is one this configuration must not have.
ios-low-deploy-target¶
Severity: Medium
Supporting iOS versions below 15 increases maintenance burden, limits modern API adoption (async/await, SwiftUI improvements), and covers a shrinking share of active devices.
How to fix: Raise the iOS deployment target to at least 15.0 in both the Podfile and Xcode project settings.
ios-lsapplicationqueries-over-limit¶
Severity: Medium–Info
A scheme past the LSApplicationQueriesSchemes cap silently reads as "app not installed" from canOpenURL — no crash, no warning, no store rejection — so a deep-link fallback or install-detection branch quietly misbehaves for exactly the schemes that overflowed the array.
How to fix: Cut LSApplicationQueriesSchemes back to 50 or fewer entries — drop schemes the app no longer queries, or replace a presence check with a universal link (open(_:options:) with .universalLinksOnly) where the target supports one.
ios-missing-encryption-declaration¶
Severity: Low
A missing export-compliance declaration blocks automated TestFlight/App Store delivery and forces a manual answer on each build, slowing releases.
How to fix: Add <key>ITSAppUsesNonExemptEncryption</key> to the app Info.plist — set <false/> if the app uses only exempt encryption (HTTPS/standard crypto), or <true/> with the required compliance documentation otherwise.
ios-missing-privacy-manifest¶
Severity: High
Apple has required a privacy manifest for App Store submissions since May 2024; a missing manifest blocks release.
How to fix: Add a PrivacyInfo.xcprivacy to the app target declaring data collection and required-reason API usage.
ios-missing-shared-scheme¶
Severity: Low
Without a committed shared scheme, xcodebuild -scheme … and CI pipelines cannot reliably select what to build, and teammates get an empty scheme list on a fresh checkout.
How to fix: In Xcode → Product → Scheme → Manage Schemes, tick “Shared” for the app scheme and commit the generated xcshareddata/xcschemes/*.xcscheme.
ios-oversized-resource¶
Severity: Varies with what is found
Large asset files increase the app bundle size, slow down downloads from the App Store, and consume more device storage.
How to fix: Compress the assets, use HEIC format for photos, or leverage asset catalogs with on-demand resources for large files.
ios-pbxproj-overview¶
Severity: Info
A large number of file references can indicate project bloat, stale references, or organizational issues.
How to fix: Periodically clean up stale file references and organize the project structure.
ios-podfile-overview¶
Severity: Info
Understanding dependency count and versioning practices helps assess maintenance burden and reproducibility.
How to fix: Review pod dependencies for unused or redundant entries.
ios-privacy-manifest-invalid¶
Severity: High–Medium
A malformed privacy manifest is not read by the build, so the defect surfaces as an App Store Connect rejection after upload rather than as a local error.
How to fix: Open the manifest in Xcode's property-list editor: NSPrivacyTracking is a Boolean that must be true whenever NSPrivacyTrackingDomains lists a domain, and every NSPrivacyAccessedAPITypes entry needs an API category plus reason codes taken from that category's own approved list — a code from another category is rejected just like an invented one. The categories and codes are Apple's, at https://developer.apple.com/documentation/bundleresources/app-privacy-configuration/nsprivacyaccessedapitypes/nsprivacyaccessedapitype (checked 2026-09-17).
ios-release-dsym-missing¶
Severity: High
Without a dSYM there is nothing to upload to Crashlytics, Sentry or App Store Connect, and every production crash report arrives as raw addresses that cannot be symbolicated. The build, the archive and the upload all succeed, so the gap is invisible until the first incident — by which point the crashes from the shipped build can never be read.
How to fix: Set DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym" for the Release configuration (the Xcode default for Release), and keep uploading the dSYM to your crash reporter.
ios-required-reason-api-undeclared¶
Severity: High
Apple rejects uploads that call a required-reason API without declaring it and a reason code (ITMS-91053). The build, the simulator and TestFlight processing all succeed, so the first signal is the rejected submission.
How to fix: Add an NSPrivacyAccessedAPITypes entry for each category with the reason code that matches the use (e.g. CA92.1 for UserDefaults read/write confined to the app, C617.1 for file timestamps of files the app created).
ios-run-script-missing-inputs-outputs¶
Severity: Low
A tool like SwiftLint that runs on every incremental build adds seconds to each compile with no benefit when nothing changed. It is a silent build-speed tax — the build stays green, it just gets slower.
How to fix: Declare Input Files and Output Files for the build phase (e.g. an output like $(DERIVED_FILE_DIR)/swiftlint.txt) so Xcode skips it when inputs are unchanged.
ios-sdk-privacy-manifest-missing¶
Severity: High
App Store Connect rejects the upload with ITMS-91061 (missing privacy manifest) or ITMS-91065 (missing signature). The build and the archive both succeed, so the first sign of it is the failed upload on release day, and the fix has to come from the SDK vendor.
How to fix: Update each SDK to a vendor release that ships its own signed PrivacyInfo.xcprivacy, or drop it. Apple's list: https://developer.apple.com/support/third-party-SDK-requirements/
ios-strip-swift-symbols-disabled¶
Severity: Low
Shipping Swift symbol metadata bloats the Release binary (roughly 10-30%) with no runtime benefit. It is a size-only regression, so nothing breaks and it goes unnoticed.
How to fix: Set STRIP_SWIFT_SYMBOLS = YES for the Release configuration (the Xcode default).
ios-swift-optimization-off-in-release¶
Severity: Medium
An unoptimized Release binary runs measurably slower and can be larger. This usually leaks in by duplicating a target or copy-pasting debug build settings, and never surfaces as a build failure.
How to fix: Set SWIFT_OPTIMIZATION_LEVEL = -O (speed) or -Osize (size) for the Release configuration.
ios-swiftlint-excessive-disabled¶
Severity: Low
Disabling too many lint rules defeats the purpose of having a linter and allows code quality issues to accumulate undetected.
How to fix: Run swiftlint rules to see rule descriptions and identify which disabled rules are still relevant. Use swiftlint --fix for auto-correctable rules, and prefer inline // swiftlint:disable:next rule_name over global disables for legitimate exceptions.
ios-uiscene-manifest-missing¶
Severity: High
Apple announced at WWDC25 that in the release following iOS 26, a UIKit app built with the latest SDK that has not adopted the UIScene life cycle will not launch. Today it is only a console warning, so the app builds, ships and passes review — and then fails to start once Apple turns the warning into an assertion.
How to fix: Add a UIApplicationSceneManifest entry to Info.plist with a UISceneConfigurations dict for UIWindowSceneSessionRoleApplication, add a UISceneDelegate, and move UI-lifecycle logic out of AppDelegate into the scene delegate (see Apple TN3187).
ios-unversioned-pods¶
Severity: Low–Info
Pods without version constraints can silently update to incompatible versions, leading to build failures or runtime bugs.
How to fix: Add version constraints to all pods, e.g., pod 'Name', '~> 1.0'.
ios-weak-permission-description¶
Severity: High
iOS shows the purpose string at the permission prompt; an empty, placeholder, or vague justification confuses users and is a common App Store review rejection reason.
How to fix: Replace each flagged purpose string with a clear, specific sentence explaining exactly why the app needs the permission.
swiftpm-branch-revision-dependency¶
Severity: Medium
A branch dependency resolves to whatever the branch points at when each machine/CI resolves it — builds silently diverge and are hard to reproduce. It is the SwiftPM equivalent of an unversioned pod or a git: pubspec dependency.
How to fix: Pin each dependency to a released version (from: "x.y.z", .upToNextMajor(from:), or exact:). Use branch/revision only for short-lived local development.