Build1 distinct publisher3 min readUpdated
A developer's App Store rejection under Guideline 2.1 traced back to one line in Apple's docs: the tracking prompt is skipped unless the app state is active, and the return value looks identical either way.
The Engineer · Build desk
Compiled by The EngineerSomething wrong?How this is made
An iOS build was rejected under Guideline 2.1 because App Review said its testers could not locate the App Tracking Transparency permission request in the build they ran, according to a writeup published on dev.to [1]. The prompt fired on the developer's own iPhone on every launch, which is the part worth paying attention to: the defect was a timing race, and the reviewer's hardware lost it [2].
The mechanism is documented. Apple's page for `requestTrackingAuthorization(completionHandler:)` says, for iOS 15 and later, that calls to the API only prompt when the application state is `UIApplicationStateActive` [3]. That is a narrower condition than "the app is in the foreground" or "my code is running." During launch there is a window in which UI and JS are executing while the app is still inactive: the splash screen going away, the first render, a modal animating in or out [4]. A request landing in that window is declined, not deferred [4].
What comes back is `notDetermined`, or `undetermined` in `expo-tracking-transparency` [5]. That is the same value you get when the user simply has not answered yet [5]. There is no thrown error, no `presented: false`, no signal of any kind that separates "iOS refused to show it" from "the sheet is still pending a decision" [6]. The author's rule of thumb is that an `undetermined` status returned immediately after an explicit authorization request is not a user declining, it is iOS never asking [7].
The rejected build stacked two ordinary mistakes. It requested authorization during startup, while the splash screen was still dismissing [8]. And it folded every non-`granted` status into "not granted," cached that for the process lifetime, and never retried [9]. The author's assessment is that either mistake is survivable alone; combined, an install whose first attempt slips never sees the prompt on that launch, and the same race can recur on the next one [10]. The developer's device was fast enough to usually reach `active` before the call landed; the review device was not [11].
The fix is to wait on observed state rather than a delay. The author's helper resolves immediately if `AppState.currentState` is already `active`, otherwise subscribes to `AppState` change events and resolves on the first `active` [12]. Two details in that helper are the useful part. It re-reads `AppState.currentState` after subscribing, because the transition can happen in the gap between the initial read and the listener being attached, which leaves you waiting on an event that already fired [13]. And it carries a 10,000 ms timeout, roughly ten seconds, so a permission helper cannot hang and drag ad SDK initialisation or the splash screen down with it [14][15].
Because "not presented" and "not answered" are indistinguishable from the return value, the author's conclusion is that the only way to tell them apart is to attempt presentation again and see whether the status changes [16].
Worth checking in your own build: whether your ATT call is gated on real app state or on a `setTimeout`, whether a non-granted status is cached for the process, and whether your slowest available test device, not your newest, is the one you validate the prompt on [3][9].
Follow any of these and your For You feed starts watching them — no settings page required.
Ranked by verification strength, evidence, and original report placement.
App Review rejected the author's iOS app under Guideline 2.1, with a note saying reviewers were unable to locate the App Tracking Transparency permission request when they tested the build.
The prompt worked on the author's own iPhone on every single launch, but did not work on the reviewers' device.
Apple's documentation for requestTrackingAuthorization(completionHandler:) states, for iOS 15 and later: "Calls to the API only prompt when the application state is UIApplicationStateActive."
UIApplication.State.active is narrower than "in the foreground" or "the code is running"; during launch there is a window where JS/UI is already executing while the app is still inactive (splash screen dismissal, first render, a modal transition animating in or out), and a call made in that window causes iOS to decline to present.
In that case the API returns notDetermined (undetermined in expo-tracking-transparency), which is the exact same value returned when the user has not answered yet.
There is no "I couldn't show it" signal, no thrown error, and no presented: false flag, so from the return value alone "the user hasn't decided yet" and "iOS silently no-op'd the request" are indistinguishable.
Evidence-backed comparisons of source perspectives and observed adoption signals. Read the methodology
Which Builder, Operator, and Investor concerns the observed source mix emphasized—not a truth score.
Evidence, demonstrated adoption, hype gap, incentives, and confidence are assessed independently, each on its own current evidence. How these are measured.
Documented API behaviour, single unverified incident
The core mechanism is strong evidence: a directly quoted Apple documentation line about UIApplicationStateActive, the ambiguous notDetermined/undetermined return value, and full code listings for both the failing pattern and the fix, all independently checkable by any reader. What is weak is the surrounding narrative - the Guideline 2.1 rejection, the reviewer device's slowness, and the fix's effectiveness are one developer's first-person account with no ticket, app identity, resubmission outcome, or corroborating publisher.
One reported incident and one self-reported fix
Adoption evidence is limited to two events from the same developer: a rejection of one build and the shipping of an AppState-gated fix using expo-tracking-transparency. There are no figures on affected installs, prompt-presentation rates, opt-in rates, or any other party reproducing the failure or reusing the helper, so real-world spread cannot be scored higher than anecdotal.
Mostly aligned, mild generalisation from n=1
The technical claims are stated at roughly the strength the evidence supports - the documented active-state requirement and the ambiguous status value are not overstated, and the fix is presented as code rather than as a guarantee. The mild overstatement is generalising from one review outcome to a broad rule that reviewer hardware is slower than developer hardware, and asserting that undetermined after an explicit request always means iOS never asked, without instrumentation data behind either.
Practitioner postmortem with ad-revenue stake
The author is the affected developer publishing on a personal developer-blogging platform, with reputational incentive to present a clean root cause and fix, and a disclosed-in-passing commercial stake: a suppressed prompt forces non-personalised-only ads and the helper is explicitly designed not to stall ad SDK initialisation. No vendor sponsorship, product placement, or paid relationship is evidenced, and the reusable code is given away, so distortion pressure is modest rather than absent.
High on mechanism, low on narrative
Confidence is high that the described platform behaviour and the shipped mitigation pattern are as stated, because both are quoted or shown as code that a reader can verify. Confidence is materially lower on the causal story and its generality: one publisher, one developer, no independent reproduction, and no before/after measurement of prompt presentation or App Review outcome.
build
Pricing the three routes to shipping iOS without a Mac, honestly1 distinct publisher
invest
Your Landed Cost Is Being Litigated By Companies With $306,000 Problems1 distinct publisher
security
Apple dates its EU app rewrite: October 1, 2026, and the install fee becomes a 5% commission1 distinct publisher
product
watchOS 27 demotes the honeycomb: five most-used apps plus Siri is the new default1 distinct publisher
Distinct publishers with included, body-backed reporting in this cluster.
dev.to
1 article · August 15, 2026