App Tracking Transparency
The App Tracking plugin requests Apple’s iOS App Tracking Transparency (ATT) prompt and exposes the current authorization status through playOps.AppTracking(). It wraps Unity’s com.unity.ads.ios-support ATT binding so your code never references Unity’s iOS-only types directly.
The plugin is a prerequisite for IDFA-backed attribution on iOS 14.5 and later — without ATT authorization, AppsFlyer (or any other MMP) cannot match clicks to installs and every install reports as Organic.
Install
The Tactile scoped registry must already be configured. Add the plugin to Packages/manifest.json dependencies:
"com.tactilegames.playops-plugins.app-tracking": "1.0.0"The plugin transitively pulls com.unity.ads.ios-support — no separate install step.
Request authorization
using PlayOps;using PlayOps.AppTracking;
// Initialize() registers every plugin, AppTracking included.// It must run BEFORE any playOps.X() accessor — and must NOT live// inside the RequestAuthorization callback.playOps.Initialize();
playOps.AppTracking().RequestAuthorization(status =>{ Debug.Log($"ATT status: {status}"); // continue boot — start MMP, ads SDK, etc.});Query current status
if (playOps.AppTracking().Status == TrackingAuthorizationStatus.Authorized){ // use IDFA-backed features}Status is safe on every platform. On non-iOS it returns NotDetermined.
Info.plist
The plugin ships a build-time post-processor that writes NSUserTrackingUsageDescription into the iOS Info.plist automatically:
Your data will be used to measure ad performance and provide a personalized experience.If you need a different wording, override the key from your own post-processor with a PostProcessBuild order higher than 1000 — the plugin’s processor runs at 1000, so anything higher wins.
Platform behavior
| Platform | Status (first run) | RequestAuthorization behavior | Native prompt shown |
|---|---|---|---|
| iOS 14.5+ device | NotDetermined | callback fires after user choice | yes |
| iOS Editor / Simulator | NotDetermined | callback fires synchronously | no |
| Android | NotDetermined | callback fires synchronously | no |
| Standalone / WebGL | NotDetermined | callback fires synchronously | no |
When the user toggles tracking off in iOS Settings
A user can disable tracking for your app at any time via Settings → Privacy & Security → Tracking → [your app]. When they do:
- Status flips from
AuthorizedtoDeniedsystem-wide, immediately. - The running app receives no callback or notification — Apple does not surface this event.
- On the next
Statusquery (or nextRequestAuthorizationcall), the plugin returnsDenied. There is no native prompt and Apple never re-prompts once the status leavesNotDetermined. - The IDFA is zeroed out for any subsequent reads.
Public surface
namespace PlayOps.AppTracking{ public enum TrackingAuthorizationStatus { NotDetermined = 0, Restricted = 1, Denied = 2, Authorized = 3 }
public sealed class AppTracking { // Constructed by AppTrackingSystemComposer at SDK boot — not part of the public surface. internal AppTracking();
public TrackingAuthorizationStatus Status { get; } public void RequestAuthorization(Action<TrackingAuthorizationStatus> completed); }
public static class PlayOpsExtension { // Throws InvalidOperationException if the plugin assembly isn't loaded; never returns null. public static AppTracking AppTracking(this PlayOpsSDK playOps); }}Numeric values match Unity.Advertisement.IosSupport.ATTrackingStatusBinding.AuthorizationTrackingStatus so the underlying cast is trivial.
playOps.AppTracking() throws InvalidOperationException if the plugin isn’t registered — either because Initialize() hasn’t run yet, or because the plugin assembly isn’t in the build. This is unlike the sibling playOps.Mmp() and playOps.Progression() accessors which return null. Those plugins are opt-in via constructor settings, so null is a meaningful “opted out” state. AppTracking registers unconditionally — a null return could only ever mean a misconfigured build, which is unrecoverable at runtime, so throwing surfaces the problem at the first call rather than letting it propagate as an opaque NullReferenceException.