Written against iOS 26/27 and visionOS 26/27. Rules and entitlement forms change; verify against Apple's current App Review Guidelines and privacy manifest documentation before you submit.
Most ARKit tutorials — including most of ours — stop at the render loop. But the thing that actually delays AR launches is rarely the tracking code. It is a camera permission prompt that half your users decline, a privacy manifest that fails validation at upload, a visionOS build that assumed world sensing was free, or a reviewer who cannot get the feature to work on a desk in Cupertino.
This is the checklist we run before a client's AR app goes to review.
1. The camera prompt is a conversion funnel
An ARKit session cannot start without camera authorization, and a declined prompt is usually permanent — the user has to go to Settings to undo it. Treat the prompt as a one-shot conversion event.
Do not let the OS ask first. Show your own explanatory screen, then trigger the system prompt when the user taps a button that means "yes, I want this":
import AVFoundation
enum CameraAccess {
static var status: AVAuthorizationStatus {
AVCaptureDevice.authorizationStatus(for: .video)
}
static func request() async -> Bool {
switch status {
case .authorized: return true
case .notDetermined:
return await AVCaptureDevice.requestAccess(for: .video)
default: return false
}
}
}
Gate the ARView behind it, and always build a real declined state — not a dead black screen:
@MainActor
final class ARGateViewModel: ObservableObject {
@Published var state: State = .explaining
enum State { case explaining, ready, denied }
func userTappedContinue() async {
state = await CameraAccess.request() ? .ready : .denied
}
}
In the .denied state, offer two things: a deep link to Settings (UIApplication.openSettingsURLString) and a non-AR fallback — a 3D turntable viewer, a photo flow, a measurement form. Never make camera denial a dead end for the whole app.
Write the usage string like a human. NSCameraUsageDescription is shown verbatim in the prompt and is read by reviewers. "This app requires camera access" is a wasted sentence. Say what it does and what it does not do:
Uses the camera to detect the floor and walls so furniture can be placed in your room. Nothing from the camera is recorded or uploaded.
If that second clause is not true for your app, do not write it. If you upload frames for server-side processing, say so plainly — reviewers compare your usage string to your actual network behavior.
Same discipline for the other strings AR apps commonly need: NSLocationWhenInUseUsageDescription for ARGeoAnchor wayfinding, NSMicrophoneUsageDescription if you capture video with sound, NSLocalNetworkUsageDescription and NSBonjourServices for peer-to-peer shared AR sessions, and NSPhotoLibraryAddUsageDescription if you let users save AR captures.
2. Privacy manifest and required-reason APIs
Every app and every third-party SDK you bundle needs a PrivacyInfo.xcprivacy. For AR apps three parts matter.
Collected data types. If your app uploads room scans, photogrammetry captures, or face-tracking output for processing, that is collected data and it must be declared, with purpose and linkage. A RoomPlan app that sends the parametric scan to your backend to generate a quote is collecting data about the user's home. Declare it.
Required-reason APIs. AR apps touch several of the categories Apple requires reasons for. The usual suspects are file timestamps (asset caches), disk space (checking room for a downloaded USDZ bundle), active keyboard, and UserDefaults. Pick the documented reason code that is actually true; a wrong-but-plausible code is a rejection waiting to happen.
Tracking and domains. If you ship an analytics or ads SDK, NSPrivacyTracking and NSPrivacyTrackingDomains have to match reality, and App Tracking Transparency applies. AR apps are unusually rich in behavioral signal — session length, room dimensions, dwell time on products — and it is easy for a well-meaning analytics integration to turn into undisclosed tracking. Audit what your event payloads actually contain.
Also check the signatures on your dependencies: any binary SDK you embed needs its own manifest and signature, and a stale AR analytics or 3D-model-delivery SDK is a common upload blocker.
3. Face tracking is a special case
If you use ARFaceTrackingConfiguration — virtual try-on, avatars, expression-driven UI — you are in the strictest corner of the rules.
- Face mesh and blend-shape data may not leave the device except as needed for the feature the user asked for, and never for advertising, profiling, or identification.
- Do not use face data to build a persistent biometric identifier. "Recognize the returning shopper's face" is not a feature you can quietly ship.
- Do not share face data with third parties, and be very careful with SDKs that receive your frame buffers.
- If children are a plausible audience, expect extra scrutiny and consider not shipping face tracking at all.
A practical pattern that keeps you clean: run the try-on entirely on device, and if the user wants to share a look, upload the rendered image they explicitly chose — never the mesh.
4. World sensing consent on visionOS
On iPhone, camera authorization is effectively the whole story. On visionOS it is not. Plane detection, scene reconstruction, image tracking, and hand data are gated behind separate authorizations, and the app never sees camera frames at all in a normal Shared Space build.
Request before you run providers, and handle refusal:
import ARKit
let session = ARKitSession()
let planes = PlaneDetectionProvider()
func start() async {
let result = await session.requestAuthorization(for: [.worldSensing])
guard result[.worldSensing] == .allowed else {
// Fall back to a windowed or volumetric experience.
return
}
try? await session.run([planes])
}
Design consequence: a visionOS app whose only mode requires world sensing has a hard failure path. Ship a degraded-but-useful mode — a volume the user can place, content anchored to the head — for people who decline. Reviewers do test the decline path.
5. Enterprise APIs need an entitlement and a real justification
The visionOS Enterprise APIs — main camera access, passthrough frame capture, higher performance headroom, barcode scanning, and similar — require a managed entitlement issued by Apple, tied to your team, and they are intended for proprietary in-house or business-to-business apps distributed to a known organization. They are not a shortcut for consumer features.
If your project plan depends on raw passthrough frames on Vision Pro, get the entitlement request in early: it is a written justification and a turnaround, not a checkbox. And keep the entitlement-dependent code isolated behind a capability check so the consumer build still compiles and ships.
6. Make the feature reviewable
The most avoidable AR rejection is "we could not reproduce the feature." The reviewer is at a desk, possibly on a simulator-hostile feature, with no marker cards, no scanned reference object, no location in your geofence, and no time.
Give them a way through:
- Demo mode. A toggle or review-account flag that loads recorded session data or a synthetic scene instead of live tracking. You probably already have this from testing with replay data — expose it.
- Assets in the review notes. Attach the printable marker for image tracking, a photo of the object to track, or a link to a PDF. Include exact steps: tap this, point at that, expect this.
- Location-independent path. If you use ARGeoAnchor, give a simulated-location build setting or a fixture location the reviewer can use in Cupertino.
- Hardware notes. State the minimum device honestly. If a feature needs LiDAR, say so, declare the right
UIRequiredDeviceCapabilities, and make sure the app degrades — not crashes — on devices without it. See LiDAR scene reconstruction. - Working credentials. An enterprise AR app behind SSO with an expired demo account is an instant rejection.
7. The rejection list we see most
- Guideline 5.1.1 — purpose string too vague or inconsistent with what the app does with the camera.
- Missing or invalid privacy manifest, or an embedded SDK without one. Caught at upload, which is worse because it blocks the whole submission.
- Feature not reproducible — no marker, no demo mode, no instructions.
- Crash on decline. Camera or world-sensing denied and the AR screen hangs or crashes.
- Requesting permissions at launch with no context, before any user intent.
- Undeclared uploads. Frames or scans posted to an analytics or ML endpoint that the privacy disclosure never mentions.
- Face data overreach, including SDKs doing something with face data the app owner did not know about.
- Enterprise-only capability in a consumer submission.
A pre-submission checklist
Before you tag the release candidate:
- Every usage string is specific, true, and matches network behavior.
- Permission prompts are preceded by an in-app explanation and triggered by user intent.
- Declined states exist for camera, location, local network, and (on visionOS) world sensing — and were tested by hand.
PrivacyInfo.xcprivacyexists for the app and every embedded SDK; required-reason codes are accurate.- App Store privacy answers match the manifest and the actual payloads.
- Face-tracking data stays on device unless the user explicitly shares a rendered result.
- Demo mode is shipped and documented in review notes; markers and reference assets are attached.
- Minimum device capabilities are declared and the no-LiDAR path was tested on a real device.
- Enterprise entitlements are approved and gated behind capability checks.
None of this is glamorous work, and none of it is optional. Budget a sprint for it rather than discovering it the week you planned to launch.
We do pre-submission reviews of ARKit and visionOS apps — privacy manifest, permission flow, review notes, and the decline paths — as a fixed-scope engagement. Get in touch if you have a submission coming up, or see how we work with product teams.