'How to add the AppTrackingTransparency permission to your iOS apps

I am extremely new to iOS, with no iOS development experience at all, however, I've been given a task that's related to preparing for iOS 14+. Based on what I've found https://support.google.com/admanager/answer/9997589, to ensure there's no loss in revenue, I need to do 2 things.

  1. Install the latest Google Mobile Ads SDK for iOS (version 7.64 or later) for AdMob or Ad Manager
  2. Add the AppTrackingTransparency permission to your iOS apps.

I've followed some guides and I'm at the point of dealing with adding the AppTrackingTransparency permission to the iOS app. This is the guide that I'm using, https://developers.google.com/admob/ios/ios14#swift.

I managed to add the key/value, shown below, in Info.plist

<key>NSUserTrackingUsageDescription</key>
<string>This identifier will be used to deliver personalized ads to you.</string>

But this is where I'm hoping to get some help. I think that I still need to add code somewhere to request user permission with AppTrackingTransparency. Based on the guide I think the following code is required to show the App Tracking Transparency dialog box. Question 1, is my assumption correct?

import AppTrackingTransparency
import AdSupport
...
func requestIDFA() {
  ATTrackingManager.requestTrackingAuthorization(completionHandler: { status in
    // Tracking authorization completed. Start loading ads here.
    // loadAd()
  })
}

Question 2, does the code live in AppDelegate.swift? Or is it really just somewhere that's suitable in the codebase? Thanks.



Solution 1:[1]

For those who might be struggling with the same things, I got the AppTrackingTransparency dialog box to appear with the function,

import AppTrackingTransparency
import AdSupport

//NEWLY ADDED PERMISSIONS FOR iOS 14
func requestPermission() {
    if #available(iOS 14, *) {
        ATTrackingManager.requestTrackingAuthorization { status in
            switch status {
            case .authorized:
                // Tracking authorization dialog was shown
                // and we are authorized
                print("Authorized")
                
                // Now that we are authorized we can get the IDFA
                print(ASIdentifierManager.shared().advertisingIdentifier)
            case .denied:
                // Tracking authorization dialog was
                // shown and permission is denied
                print("Denied")
            case .notDetermined:
                // Tracking authorization dialog has not been shown
                print("Not Determined")
            case .restricted:
                print("Restricted")
            @unknown default:
                print("Unknown")
            }
        }
    }
}
//

I then simply called the function requestPermission() on the app's login page, so users see the permission dialog before signing in. Without calling the function, the dialog box show in this guide, https://developers.google.com/admob/ios/ios14, doesn't appear for me.

This article has an example github project: https://medium.com/@nish.bhasin/how-to-get-idfa-in-ios14-54f7ea02aa42

Solution 2:[2]

In iOS 15 it can only be requested with ATTrackingManager.requestTrackingAuthorization if the application state is already active, so it should be moved from didFinishLaunchingWithOptions to applicationDidBecomeActive.

Solution 3:[3]

Your complete guide to ATT (updated for iOS 15) ?

tl;dr

func applicationDidBecomeActive(_ application: UIApplication) {
    if #available(iOS 14, *) {
        ATTrackingManager.requestTrackingAuthorization { status in
            switch status {
                case .authorized:
                    print("enable tracking")
                case .denied:
                    print("disable tracking")
                default:
                    print("disable tracking")
            }
        }
    }
}

Long answer

All new apps submitted to the App Store need to follow App Tracking Transparency guidelines in iOS 14.0+. These guidelines form part of new Apple privacy guidelines. The main idea is for users to be given control of whether all apps can track them, some apps can track them, and make the privacy policies of the apps Transparent on download. :+1: Apple :wink:

1. Add framework in Xcode

This is possible by navigating to <PROJECT_NAME>.xcproject / <PROJECT_NAME>.xcworkspace -> General -> Frameworks, Libraries, and Embedded Content.

General Xcode Tab with ATT.framework

2. Add NSUserTrackingUsageDescription

This is a String key that needs to be added to Info.plist.

3. ATTrackingManager.requestTrackingAuthorizationWithCompletionHandler:

This function is advised on the first app launch to ensure the value is captured. The prompt only shows if the app is a fresh install and the user consent status is unknown.

For the majority of applications, only enable tracking if the status is authorized on becoming active (new in iOS 15), as below:

import AppTrackingTransparency

class AppDelegate: UIApplicationDelegate {

    func applicationDidBecomeActive(_ application: UIApplication) {
        if #available(iOS 14, *) {
            ATTrackingManager.requestTrackingAuthorization { status in
                switch status {
                    case .authorized:
                        print("enable tracking")
                    case .denied:
                        print("disable tracking")
                    default:
                        print("disable tracking")
                }
            }
        }
    }
}

NOTE: UI Logic needs to be wrapped on the DispatchQueue.main queue because the completion block currently executes on a concurrent DispatchQueue.

4. ATTrackingManager.trackingAuthorizationStatus

Track changes to consent via ATTrackingManager.trackingAuthorizationStatus which has 4 possible enum values:

  1. ATTrackingManagerAuthorizationStatusAuthorized - Granted consent.
  2. ATTrackingManagerAuthorizationStatusDenied - Denied consent.
  3. ATTrackingManagerAuthorizationStatusNotDetermined - Unknown consent.
  4. ATTrackingManagerAuthorizationStatusRestricted - Device has an MDM solution applied. Recommend handling this the same as consent denied until vendor provides consent explicitly.

5. Track the user's consent status on internal servers

If you are capturing your own analytics, this step is necessary because the user can toggle consent at any time using iOS Settings.

3rd-Party Analytics

Recommendation

It's safer to disable Firebase Analytics, Flurry Analytics, or other Analytics providers from the configuration on app launch after reading the status value when consent is denied/restricted.

Why so? (asked in comments):

  1. Even if the frameworks don't track users for the moment, the SDKs may update and alter their policies down the line, and your code will not be synced automatically.
  2. There are lots of nuances in the Apple privacy legislation, and it's better to avoid the risk of getting store blacklisted IMHO.
  3. Technically you can hide from the Apple privacy guidelines, but do you want to get exposed when they start verifying the accuracy of your App Store statements? This could lead to permanent blacklisting.

Example Firebase policy (26.06.20)

To answer your questions:

  • No, Analytics will not access the ad ID if no advertising SDKs are present and AdSupport is not linked. However, SafariServices on iOS 11 imports the AdSupport framework, causing device advertisement identifier reporting. #1686 (GH issue #) makes it so that explicit ad ID access control is required, which is something we need to add on the Firebase side.
  • Yes, if you're using Analytics with an ad framework you should follow Apple's guidelines. You may not be able to submit to the App Store otherwise.

Other Option

SO, yes it is possible to work around (like tax avoidance) declaring whether you use analytics. This could be worth it if your public image is of importance to you on the App Store.

However, to be honest, I would probably remove 3rd Party Analytics going forward and stick to App Store Analytics for a better experience instead (if possible) here. Many companies heavily rely on analytics data, and if you aren't able to migrate away, transparently declare your analytics usage.

Another ideal strategy would be to fully revamp or rapidly A/B test the app before launching live - use beta modes. Analytics in the App Store is more than enough for live apps.

I can't quantify the risk for being blacklisted subsequently though, as this is library-specific and also depends on your release workflow (do you check for changes in SDK policies?).

Important Note

Wrap

if #available(iOS 14.0, *)

Wherever you call the ATTrackingManager because the request will not complete on older iOS versions ?. Track consent on older iOS versions with your own backend flags, or locally on the device.

Apple suggests implementing the new ATT requirements as soon as possible if you track users because you will be blocked from new updates to the App Store in the meantime, even with production crashes. Not only will your users be happier, but your App Store ranking improves if you update your app regularly.

Want to toggle user consent in the app? See here for more info.

Solution 4:[4]

What's happen if the user doesn't give tracking permission to the app? then when the user doesn't allow tracking permission, you can't get IDFA then Your revenue will be down again...for more see Answer

To Solve itthe Google Mobile Ads SDK supports conversion tracking using Apple's SKAdNetwork, which lets Google attribute an app install even when the IDFA is not available.

SKAdNetwork Solutions for Advertisers to Get out-of-the-box support for Appleā€™s SKAdNetwork and maximize your future growth on iOS 14 & beyond.

To fix it, enable SKAdNetwork to track conversions

The Google Mobile Ads SDK supports conversion tracking using Apple's SKAdNetwork, which lets Google attribute an app install even when the IDFA is not available.

To enable this functionality, update the SKAdNetworkItems key with an additional dictionary that defines Google's SKAdNetworkIdentifier value in your Info.plist.

    <key>SKAdNetworkItems</key>
  <array>
    <dict>
      <key>SKAdNetworkIdentifier</key>
      <string>cstr6suwn9.skadnetwork</string>
    </dict>
    <dict>
      <key>SKAdNetworkIdentifier</key>
      <string>4fzdc2evr5.skadnetwork</string>
    </dict>
    <dict>
      <key>SKAdNetworkIdentifier</key>
      <string>2fnua5tdw4.skadnetwork</string>
    </dict>
    <dict>
      <key>SKAdNetworkIdentifier</key>
      <string>ydx93a7ass.skadnetwork</string>
    </dict>
    <dict>
      <key>SKAdNetworkIdentifier</key>
      <string>5a6flpkh64.skadnetwork</string>
    </dict>
    <dict>
      <key>SKAdNetworkIdentifier</key>
      <string>p78axxw29g.skadnetwork</string>
    </dict>
    <dict>
      <key>SKAdNetworkIdentifier</key>
      <string>v72qych5uu.skadnetwork</string>
    </dict>
    <dict>
      <key>SKAdNetworkIdentifier</key>
      <string>c6k4g5qg8m.skadnetwork</string>
    </dict>
    <dict>
      <key>SKAdNetworkIdentifier</key>
      <string>s39g8k73mm.skadnetwork</string>
    </dict>
    <dict>
      <key>SKAdNetworkIdentifier</key>
      <string>3qy4746246.skadnetwork</string>
    </dict>
    <dict>
      <key>SKAdNetworkIdentifier</key>
      <string>3sh42y64q3.skadnetwork</string>
    </dict>
    <dict>
      <key>SKAdNetworkIdentifier</key>
      <string>f38h382jlk.skadnetwork</string>
    </dict>
    <dict>
      <key>SKAdNetworkIdentifier</key>
      <string>hs6bdukanm.skadnetwork</string>
    </dict>
    <dict>
      <key>SKAdNetworkIdentifier</key>
      <string>prcb7njmu6.skadnetwork</string>
    </dict>
    <dict>
      <key>SKAdNetworkIdentifier</key>
      <string>wzmmz9fp6w.skadnetwork</string>
    </dict>
    <dict>
      <key>SKAdNetworkIdentifier</key>
      <string>yclnxrl5pm.skadnetwork</string>
    </dict>
    <dict>
      <key>SKAdNetworkIdentifier</key>
      <string>4468km3ulz.skadnetwork</string>
    </dict>
    <dict>
      <key>SKAdNetworkIdentifier</key>
      <string>t38b2kh725.skadnetwork</string>
    </dict>
    <dict>
      <key>SKAdNetworkIdentifier</key>
      <string>7ug5zh24hu.skadnetwork</string>
    </dict>
    <dict>
      <key>SKAdNetworkIdentifier</key>
      <string>9rd848q2bz.skadnetwork</string>
    </dict>
    <dict>
      <key>SKAdNetworkIdentifier</key>
      <string>n6fk4nfna4.skadnetwork</string>
    </dict>
    <dict>
      <key>SKAdNetworkIdentifier</key>
      <string>kbd757ywx3.skadnetwork</string>
    </dict>
    <dict>
      <key>SKAdNetworkIdentifier</key>
      <string>9t245vhmpl.skadnetwork</string>
    </dict>
    <dict>
      <key>SKAdNetworkIdentifier</key>
      <string>2u9pt9hc89.skadnetwork</string>
    </dict>
    <dict>
      <key>SKAdNetworkIdentifier</key>
      <string>8s468mfl3y.skadnetwork</string>
    </dict>
    <dict>
      <key>SKAdNetworkIdentifier</key>
      <string>av6w8kgt66.skadnetwork</string>
    </dict>
    <dict>
      <key>SKAdNetworkIdentifier</key>
      <string>klf5c3l5u5.skadnetwork</string>
    </dict>
    <dict>
      <key>SKAdNetworkIdentifier</key>
      <string>ppxm28t8ap.skadnetwork</string>
    </dict>
    <dict>
      <key>SKAdNetworkIdentifier</key>
      <string>424m5254lk.skadnetwork</string>
    </dict>
    <dict>
      <key>SKAdNetworkIdentifier</key>
      <string>uw77j35x4d.skadnetwork</string>
    </dict>
    <dict>
      <key>SKAdNetworkIdentifier</key>
      <string>e5fvkxwrpn.skadnetwork</string>
    </dict>
    <dict>
      <key>SKAdNetworkIdentifier</key>
      <string>zq492l623r.skadnetwork</string>
    </dict>
    <dict>
      <key>SKAdNetworkIdentifier</key>
      <string>3qcr597p9d.skadnetwork</string>
    </dict>
  </array>

Solution 5:[5]

According to the link you posted:

https://developers.google.com/admob/ios/ios14#swift.

The main thing to avoid loss of revenue from AdMob is to add this into Info.plist:

<key>SKAdNetworkItems</key>
<array>
  <dict>
    <key>SKAdNetworkIdentifier</key>
    <string>cstr6suwn9.skadnetwork</string>
  </dict>
</array>

I don't know when/if AppTrackingTransparency permission is required. I know Apple say you need it but they don't give a deadline. Google say "If you decide to include App Tracking Transparency" which to me is hinting to not bother!

Solution 6:[6]

We are using Firebase and Facebook, to get the app approved we put both calls "behind" the ATT protection, that is:

For Facebook (from AppDelegate):

if #available(iOS 14, *) {
    ATTrackingManager.requestTrackingAuthorization { status in
        if status == .authorized {
                ApplicationDelegate.shared.application(
                    application,
                    didFinishLaunchingWithOptions: launchOptions
                )
        }
    }
}

For Firebase:

if #available(iOS 14, *) {
    ATTrackingManager.requestTrackingAuthorization { status in
        if status == .authorized {
            Analytics.logEvent(eventName, parameters: [:])
        }
    }
}

As for the firebase token for the notifications, we did not include them behind the protection and had no issue.

Solution 7:[7]

  1. Open info.plist file, add SKAdNetworkIdentifier and NSUserTrackingUsageDescription. The following one is only for Google (Admob), you can find the full list here.

    <key>SKAdNetworkItems</key>
    <array>
        <dict>
            <key>SKAdNetworkIdentifier</key>
            <string>cstr6suwn9.skadnetwork</string>
        </dict>
    </array>
    
    //..
    
    <key>NSUserTrackingUsageDescription</key>
    <string>This identifier will be used to deliver personalized ads to you.</string>
    
  2. Request the ATT dialog. (For simplicity I'm requesting it right after the app loads)

    #import <AppTrackingTransparency/AppTrackingTransparency.h>
    #import <AdSupport/AdSupport.h>
    
    //...
    
    - (BOOL)application:(UIApplication *)application
        didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
        // ...
    
        if (@available(iOS 14.0, *))
        {
            [ATTrackingManager requestTrackingAuthorizationWithCompletionHandler:^(ATTrackingManagerAuthorizationStatus status) {
            // Tracking authorization completed. Start loading ads here.
            // [self loadAd];
          }];
    
      return [super application:application didFinishLaunchingWithOptions:launchOptions];
    
    }
    

Solution 8:[8]

func askPermission() {
    if #available(iOS 14, *) {
        ATTrackingManager.requestTrackingAuthorization { (status) in
            //handled
            print(status)
            if status == .authorized{
                
                Settings.shared.isAdvertiserTrackingEnabled = true
                
            }
        }
    }
}

I don't know but for me this piece of

Solution 9:[9]

As a completion on @mark answer you also should add permission statement to plist file in key Privacy - Tracking Usage Description

Solution 10:[10]

I have observed that if the following method

"ATTrackingManager requestTrackingAuthorizationWithCompletionHandler()"

is called from AppDelegate then sometimes "ATTrackingManagerAuthorizationStatusNotDetermined" is returned as a status and an alert is not shown.
My phone's iOS version is "iOS-15.0.2"

As a solution, we can call "requestTrackingAuthorization..." method after splash screen or after appearing LandingPage.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Mark
Solution 2 shim
Solution 3
Solution 4 Paresh Mangukiya
Solution 5
Solution 6 pkamb
Solution 7 CopsOnRoad
Solution 8 stm apps
Solution 9 Sherif Kamal
Solution 10