RORK LABJP
BUILD — Rork Max runs real Macs in the cloud loaded with Xcode and the iOS SDK, writing SwiftUI, compiling, reading the errors and building again. That loop, not the code generation, is what lifts the outputNATIVE — What comes out is pure Swift and SwiftUI, not React Native. Reaching AR, Metal graphics and widgets that React Native cannot touch is the real gap between this and other buildersPLATFORMS — Coverage spans iPhone, iPad, Apple Watch, Apple TV and Vision Pro, plus iMessage. Worth a look if you want to start from a watch app or an extension rather than a phone screenCOMPANION — The Rork Companion app lets you check a generated build on a real iPhone without a paid Apple Developer account, lowering the bar for trying a first project end to endPRICING — Free to start, paid plans from $25 a month, and Rork Max on the $200 Max plan. Worth working out up front how many projects it takes to earn that backDEADLINE — From August 31, 2026, Google Play requires target API level 36 or higher for new apps and updates alike. Ten days out, and the targetSdkVersion of what you generate is yours to verifyBUILD — Rork Max runs real Macs in the cloud loaded with Xcode and the iOS SDK, writing SwiftUI, compiling, reading the errors and building again. That loop, not the code generation, is what lifts the outputNATIVE — What comes out is pure Swift and SwiftUI, not React Native. Reaching AR, Metal graphics and widgets that React Native cannot touch is the real gap between this and other buildersPLATFORMS — Coverage spans iPhone, iPad, Apple Watch, Apple TV and Vision Pro, plus iMessage. Worth a look if you want to start from a watch app or an extension rather than a phone screenCOMPANION — The Rork Companion app lets you check a generated build on a real iPhone without a paid Apple Developer account, lowering the bar for trying a first project end to endPRICING — Free to start, paid plans from $25 a month, and Rork Max on the $200 Max plan. Worth working out up front how many projects it takes to earn that backDEADLINE — From August 31, 2026, Google Play requires target API level 36 or higher for new apps and updates alike. Ten days out, and the targetSdkVersion of what you generate is yours to verify
Articles/Dev Tools
Dev Tools/2026-04-30Intermediate

Implementing App Tracking Transparency in Rork: How to Pass App Store Review

How to implement App Tracking Transparency (ATT) in Rork-built iOS apps so they pass App Store review on the first try, with the right Info.plist copy, prompt timing, and graceful fallback when users deny tracking.

Rork539App Tracking Transparency3ATT9iOS110App Store Review8expo-tracking-transparencyPrivacy8

ATT Is Where iOS Submissions Quietly Get Stuck

You finish your Rork app, push it to TestFlight, submit to the App Store, and a few days later get a rejection note that says your App Tracking Transparency disclosure is incomplete. I have lived through this loop three times. The code itself was fine — what tripped me up was the combination of three things: the wording in Info.plist, the moment the prompt appears, and how the app behaves when the user taps "Don't Allow."

ATT became mandatory in iOS 14.5. If your app reads the IDFA or shares any identifier that lets you track a user across other apps and websites, Apple now requires explicit, in-app consent. The moment you drop in AppsFlyer, Adjust, AdMob, Meta Audience Network, or any similar SDK, you are in scope — even if you never directly read the advertising identifier yourself.

This guide walks through implementing ATT in a Rork-based app (React Native + Expo) so it passes review on the first try. We will cover not just the code with expo-tracking-transparency, but the wording, timing, and fallback strategy that reviewers actually scrutinize. By the end you should be able to ship a build with ATT plumbing that survives both an automated pre-check and a human reviewer.

Three Common Cases Where You Need ATT

Before writing any code, confirm whether ATT applies to your app. You need it if any of the following is true.

  • You use an ad SDK: AdMob, Meta Audience Network, Unity Ads, AppLovin, and basically every monetization network read or attempt to read the IDFA
  • You use an attribution SDK: AppsFlyer, Adjust, Branch, Singular, or anything that tracks where installs came from across other apps and the web
  • You use analytics that reads IDFA: Firebase Analytics with the advertising ID enabled, certain Mixpanel configurations, or any setup where you ship the IDFA to a remote analytics service

If your app only uses an internally generated UUID stored on your own server and never touches IDFA or shares identifiers with third parties, you do not need to show the ATT prompt. That said, Apple inspects edge cases carefully, so always check each SDK's documentation for whether it accesses IDFA. A good test: scan your Podfile.lock (or the iOS build output) for any SDK whose name appears in your privacy nutrition label as collecting "identifiers." If something is listed there, ATT almost certainly applies.

Step 1: Add the Disclosure String to Info.plist

In a Rork project, edit app.json (or app.config.js) and set ios.infoPlist.NSUserTrackingUsageDescription.

{
  "expo": {
    "ios": {
      "bundleIdentifier": "com.example.myapp",
      "infoPlist": {
        "NSUserTrackingUsageDescription": "Allow this app to use activity data so we can show ads tailored to you and keep the app free. You can keep using every feature even if you decline."
      }
    }
  }
}

The wording matters more than most developers think. Reviewers look for three things.

  1. A specific reason: "For ads" or "for analytics" alone is too vague. State why the user benefits — ideally that the app stays free
  2. A clear statement that decline does not break the app: Wording that implies tracking is required gets rejected, because Apple's guideline explicitly forbids gating features behind ATT consent
  3. No misleading framing: "To improve our service" alone is too soft when the SDK actually reads the advertising ID. Reviewers will compare your disclosure string against the privacy nutrition label

The pattern that has consistently passed for me always names ads, free usage, and full functionality on decline. Keep the string under about 150 characters so it fits comfortably in the system dialog without truncation on smaller devices.

Step 2: Trigger the Prompt with expo-tracking-transparency

Rork projects can pull in Expo modules, so expo-tracking-transparency is the safest path. Add a small helper near your app's entry point.

// app/_layout.tsx or similar
import {
  getTrackingPermissionsAsync,
  requestTrackingPermissionsAsync,
} from 'expo-tracking-transparency';
import { Platform } from 'react-native';
 
export async function ensureTrackingPermission(): Promise<boolean> {
  // iOS only — Android is not in scope
  if (Platform.OS !== 'ios') return true;
 
  // On iOS < 14.5 the API is unavailable, so treat as granted
  const current = await getTrackingPermissionsAsync();
  if (current.status === 'granted') return true;
  if (current.status === 'denied') return false;
 
  // Only prompt when the user has not yet decided
  const result = await requestTrackingPermissionsAsync();
  return result.status === 'granted';
}

The key detail is calling requestTrackingPermissionsAsync only when the status is undetermined. Once a user has answered "Don't Allow," calling this method again will not show the prompt — it just returns denied. To re-prompt, the user has to flip the toggle in iOS Settings under "Privacy & Security → Tracking." A common mistake is wrapping the call in a useEffect that fires on every mount, which leads to silent denials and inflates your "rejected" metric for no real reason. Cache the answer in module state and treat the first determination as final until the user explicitly asks to change it.

Step 3: Time the Prompt Right

Apple recommends prompting "at the appropriate time," and reviewers will reject apps that fire it the moment the app launches. The pattern that has worked best for me looks like this.

  1. On first launch, show onboarding screens — no prompt yet, just introduce what the app does
  2. At the end of onboarding, add a single screen that explains: "We keep this app free by showing ads. The next dialog asks if you'd like ads tailored to your interests. Choose 'Allow Tracking' to opt in."
  3. The "Continue" button on that screen calls requestTrackingPermissionsAsync()

This pre-prompt pattern roughly doubles the opt-in rate in my experience, going from somewhere around 25–30% to 50% or higher depending on the audience. Apple endorses it in their official sample code, which also means reviewers tend to recognize it as a good-faith pattern. Just be careful that your pre-prompt does not visually mimic the system dialog — that crosses into "deceptive UI" territory and will get rejected. Use your own brand styling, your own button labels, and a clear continue/skip option that does not pre-bias the user.

Step 4: Handle "Don't Allow" Gracefully

If a user declines, you cannot lock them out of features or refuse to show ads (App Review Guideline 3.2.2 prohibits this). The right move is to switch to non-personalized advertising and disable any IDFA-based logic on the attribution side.

import mobileAds, { MaxAdContentRating } from 'react-native-google-mobile-ads';
 
export async function configureAds(): Promise<void> {
  const trackingAllowed = await ensureTrackingPermission();
 
  await mobileAds().setRequestConfiguration({
    maxAdContentRating: MaxAdContentRating.T,
    tagForChildDirectedTreatment: false,
  });
 
  // Pass the flag to each ad request, e.g.:
  // const request = { requestNonPersonalizedAdsOnly: !trackingAllowed };
 
  await mobileAds().initialize();
}

For AdMob, every ad request should carry requestNonPersonalizedAdsOnly: !trackingAllowed. Attribution SDKs like AppsFlyer and Adjust expose options such as disableIDFA that you can set based on the user's choice. Some SDKs also expose a setEnabled(false) style toggle that you can flip on the fly — call this immediately after ensureTrackingPermission() resolves so the SDK never sends an IDFA on the first request.

iOS also offers SKAdNetwork, Apple's own framework for measuring installs without IDFA. You lose 1:1 user-level attribution, but campaign-level performance still works. Any app spending real money on user acquisition should plan to support SKAdNetwork alongside the IDFA flow. Most of the major attribution SDKs now wrap SKAdNetwork (and its successor AdAttributionKit) for you, but you still need to register your campaigns in the SDK dashboard and add the network IDs to your Info.plist under SKAdNetworkItems.

Step 5: The Specific Things That Got Me Rejected

Beyond the textbook setup, here are the rejection notes I actually received. Knowing the patterns up front can save you a submission cycle.

  • The disclosure string did not mention "ads": Saying "to improve our service" alone got flagged because the SDK list clearly included ad networks. The string must match what is actually happening, and Apple cross-references it against your privacy nutrition label
  • The prompt fired before onboarding: Reviewers called this "user is presented with the prompt without context." Adding a single explanatory screen fixed it on the next submission
  • No path back to Settings: Apps without a way to deep-link into Settings (Linking.openURL('app-settings:')) get noted in internal review. Add a row in your Settings screen for users who want to change their decision later

None of these are written explicitly in Apple's developer documentation — they are reviewer judgment calls. Reading enough rejection feedback, though, you start to see the same three issues over and over. If you keep a small Notion page with rejection notes from your past submissions, you build up a personal checklist faster than any official doc can teach you.

Start by Re-reading Your Info.plist Copy

ATT compliance is decided more by your wording than by your code. Before you ship anything, open app.json and check that NSUserTrackingUsageDescription mentions ads, free access, and the fact that declining keeps every feature working. Just doing that meaningfully drops your rejection probability.

If you are working through the broader attribution stack, the Rork attribution guide for AppsFlyer, Adjust, and SKAdNetwork covers how the choice flows downstream. For permission UX patterns more generally, the Rork Face ID / Touch ID biometric auth guide walks through similar prompt timing decisions. And before submitting, the Rork App Store review checklist is worth a final pass to catch anything else reviewers tend to flag.

Share

Thank You for Reading

Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

Dev Tools2026-05-07
Rork iOS App: Why Your App Tracking Transparency Prompt Never Shows Up — and How to Fix It
Three real causes the ATT (App Tracking Transparency) dialog never appears in Rork-generated iOS apps — Info.plist, call timing, and AdMob init order — with working code and on-device verification steps.
Dev Tools2026-04-21
Lifting ATT Opt-in Rates in Rork Apps — Turning iOS Tracking Consent into Real Ad Revenue
Implementation patterns that raise App Tracking Transparency (ATT) opt-in rates in Rork-built iOS apps, wired to your AdMob revenue. Covers purpose string copy, prompt timing, a pre-permission screen, cohort-level measurement, UMP (GDPR) ordering, and a dynamic non-personalized ads fallback.
Dev Tools2026-06-29
Handling iOS Limited Photo Library Access in a Rork (Expo) App
Handle iOS limited photo library access (selected photos only) correctly in a Rork (Expo) app. Covers the three states of full / limited / denied, designing a screen that works from the selected subset, and a path to add more photos, all with working code.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →