Guideline 2.1 rejection: App Completeness
How the rejection usually reads: "We discovered one or more bugs in your app" / "Information Needed" / "the app crashed on launch"
Why it happens
2.1 is the single most common rejection because it is a catch-all: crashes, dead links, placeholder content, a demo account that does not work, a feature the description promises but the build does not have. The reviewer runs the app on a real device for a few minutes and reports whatever breaks. The frustrating part is that most 2.1 rejections are mechanical and visible in the build before you upload: a framework that was not embedded (crash on launch), a support URL that returns 404, a leftover localhost address from development. Fix what they named, then look for what they did not name — on the next round they only re-check the diff.
What can be checked before you submit
7 of RejectProof’s checks map to Guideline 2.1. Each one reads your .ipa — no upload — and reports the evidence with a fix.
- Development server references left in the build
PB-30 · guideline 2.1 · likely rejection
This build still points at a local Metro / Expo dev server. Build a release binary: Expo → `eas build --profile production`; bare RN → Release configuration with a bundled JS file; make sure no exp:// or localhost:8081 URLs remain. Reviewers cannot reach your machine, so the app fails to load and is rejected under 2.1.
- Dead links inside the app
PB-33 · guideline 2.1 · probable
Every link the reviewer can tap must open a working page (support, privacy policy, terms, help, in-app WebView pages). Fix or remove the URLs listed in the evidence, then rebuild — App Review opens them in Safari and rejects under 2.1 (App Completeness) when they 404. For links only visible inside a WKWebView, load that page on a device with the production build before submitting.
- Embedded framework missing — crash on launch
PB-34 · guideline 2.1 · likely rejection
The executable links a dynamic framework that is not inside the .app bundle, so dyld aborts before your code runs ("Library not loaded: @rpath/…" in the crash log) and App Review rejects under 2.1 (App Completeness). Xcode: select the target → General → Frameworks, Libraries, and Embedded Content → set the framework to "Embed & Sign" (not "Do Not Embed"); for CocoaPods run `pod install` again and make sure use_frameworks! matches how the pod is built. Expo/EAS: remove the framework from any manual `ios/` edits or config plugin, run `npx expo prebuild --clean`, then `eas build --platform ios --profile production`. Static libraries (.a) and XCFrameworks marked static do not need embedding; only dynamic frameworks listed in the evidence do.
- Missing arm64 slice / unsupported architecture
PB-35 · guideline 2.1 / 2.5.1 · likely rejection
App Store builds must contain a device (arm64) slice built for the iOS platform. A simulator build (x86_64, or arm64 with the iOS Simulator platform) fails to install on devices and is rejected under 2.1 / 2.5.1. Xcode: Product → Archive with destination "Any iOS Device (arm64)", not a simulator; check Build Settings → Architectures = Standard, Excluded Architectures does not exclude arm64, and Build Active Architecture Only = No for Release. Expo/EAS: use `eas build --platform ios --profile production` (a device build); never upload a build made with `"simulator": true` in eas.json, and do not submit `.app` folders from `npx expo run:ios`.
- Minimum iOS in Info.plist does not match the binary
PB-36 · guideline 2.1 · warning
Info.plist MinimumOSVersion, the executable's deployment target (LC_BUILD_VERSION minos) and every embedded framework's MinimumOSVersion must agree. When a framework requires a newer iOS than the app declares, the app installs on older devices and dies at launch with "Symbol not found" / "Library not loaded"; App Review tests on the oldest iOS you declare and rejects under 2.1. Xcode: set one iOS Deployment Target at the project level and let all targets inherit it; for CocoaPods add `platform :ios, 'X.Y'` to the Podfile and re-run `pod install`; for SPM packages check their minimum platform. Expo/EAS: set `expo-build-properties` → ios.deploymentTarget in app.json (or `ios.deploymentTarget` in Podfile.properties.json), then `npx expo prebuild --clean` and rebuild.
- WKWebView links open in a new window but nothing handles the popup
PB-42 · guideline 2.1 · warning
In a WKWebView, a link with target="_blank" (or window.open) does nothing unless the app implements WKUIDelegate's webView(_:createWebViewFor:navigationAction:windowFeatures:) — the tap silently fails and App Review reports it as a broken feature under 2.1. Either implement that delegate method (load the request in the same web view or open it in Safari), or strip target="_blank" from the web content. react-native-webview: set setSupportMultipleWindows={false} or handle onOpenWindow.
- Localized resources shipped but CFBundleLocalizations not declared
PB-43 · guideline 2.1 / 2.3.3 · warning
iOS picks the app language from CFBundleLocalizations (and CFBundleDevelopmentRegion) in Info.plist, not from the .lproj folders you ship. Without the key, a reviewer's device can open the app in the wrong language and the rejection reads like a vague 2.1 'does not match' note. Declare every shipped language: Expo: app.json → expo.ios.infoPlist.CFBundleLocalizations: ["en", "ar"] (or expo-localization's config plugin); Xcode: project → Info → Localizations. Also read the device language at first launch instead of hardcoding one.
What a scan cannot tell you
Static checks read the build; they do not run it. Whether a reviewer likes your design, agrees with your content, or considers the app “sufficiently different” is their call. Fix the mechanical layer first so the human review is about your product, not about a missing plist key.