'IDFA iOS14 returns denied without displaying auth popup

I am doing a research on getting the IDFA on iOS 14. I am using iPhone 8 Plus.

I have added

<key>NSUserTrackingUsageDescription</key>
<string>App would like to access IDFA for tracking purpose</string>

in the .plist file.

Then added

let type = ATTrackingManager.trackingAuthorizationStatus;

which returns .denied, having

func requestPermission() {
        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")
            }
        }
    }

But I'm getting .denied without any popup.

Do you know what is happening?



Solution 1:[1]

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

Solution 2:[2]

If global setting Allow Apps to Request to Track is OFF, requestTrackingAuthorization will return .denied immediately.

But for some user even after Allow Apps to Request to Track is ON, requestTrackingAuthorization is returning .denied.

This is OS issue which is fixed in 14.5.1 release so just update your OS to get ATT dialog.

Release notes for iOS and iPadOS 14.5.1

This update fixes an issue with App Tracking Transparency where some users who previously disabled Allow Apps to Request to Track in Settings may not receive prompts from apps after re-enabling it. This update also provides important security updates and is recommended for all users.

Solution 3:[3]

SwiftUI iOS 15+

To show the Alert for ATTrackingManager.requestTrackingAuthorization you must ensure that you call this when the application is in a active state(or alert will not show)

To do this I am using a Publisher to check for this

ContentView()
            .onReceive(NotificationCenter.default.publisher(for: UIApplication.didBecomeActiveNotification)) { _ in
                if ATTrackingManager.trackingAuthorizationStatus == .notDetermined {
                    ATTrackingManager.requestTrackingAuthorization { status in
                        //Whether or not user has opted in initialize GADMobileAds here it will handle the rest
                                                                    
                        GADMobileAds.sharedInstance().start(completionHandler: nil)
                        }
                } else {
                    GADMobileAds.sharedInstance().start(completionHandler: nil)
                    
                }

Solution 4:[4]

Because the permission is denied from User

You can move user to app'setting page by my code

UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!, options: [:], completionHandler: nil)

Solution 5:[5]

You need to move your code to AppDelegate, and run it when the app becomes active, like @Serg Smyk said.

 func applicationDidBecomeActive(_ application: UIApplication) {
    if #available(iOS 14, *) {
        ATTrackingManager.requestTrackingAuthorization { status in
            switch status {
            case .authorized:
                // Tracking authorization dialog was shown
                // and we are authorized
                print("Authorized")
            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")
            }
        }
    }
}

In SwiftUI:

Add a property to catch the ScenePhase, then:

@Environment(\.scenePhase) var scenePhase

struct ContentView: View {
    @Environment(\.scenePhase) var scenePhase

    var body: some View {
        Text("Hello, world!")
            .padding()
            .onChange(of: scenePhase) { newPhase in
                if newPhase == .active {
                    print("Active")
                    if #available(iOS 14, *) {
                        ATTrackingManager.requestTrackingAuthorization { status in
                            switch status {
                            case .authorized:
                                // Tracking authorization dialog was shown
                                // and we are authorized
                                print("Authorized")
                            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")
                            }
                        }
                    }
                } else if newPhase == .inactive {
                    print("Inactive")
                } else if newPhase == .background {
                    print("Background")
                }
            }
    }
}
        

Solution 6:[6]

Silly question from my side, are you actually calling the function? Keep in mind, once is answered, it won't show up again. You will have to delete and install the app to present it again

Solution 7:[7]

I am requesting the tracking permission straight after the Push Notifications permission dialogue was hidden (completionHandler). It was always returning denied, strangely enough adding a one second delay fixed it for me.

DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
  // Your dialog
}

Solution 8:[8]

In my case I had to ask permissions consecutively so IDFA was return .denied without showing the alert even global setting of Allow Apps to Request to Track was ON then I found this solution.

@available(iOS 14, *)
static func requestIDFAPermision(withDelay delay: TimeInterval, completion: ((_ isGranted: Bool) -> Void)? = nil) {
            
    let threadQueue = DispatchQueue.main
    let idfaStatus = ATTrackingManager.trackingAuthorizationStatus
    threadQueue.async {
        switch idfaStatus {
        case .authorized:
            completion?(true)
            break
        case .denied:
            completion?(false)
            break
        case .notDetermined: // Give some delay then request
            threadQueue.asyncAfter(deadline: .now() + delay, execute: {
                ATTrackingManager.requestTrackingAuthorization { status in
                    switch status {
                    case .authorized:
                        completion?(true)
                    case .denied, .restricted:
                        completion?(false)
                        break
                    default:
                        completion?(false)
                        break
                    }
                }
            })
            break
        default:
            completion?(false)
            break
        }
    }
}

I'm calling this function inside viewDidAppear() after asking for Notification permission like this:

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    
    PermissionsProvider.requestNotificationPermision { isGranted in
        if isGranted {
            print("User granted for push notifications.")
        }
        
        if #available(iOS 14, *) {
            PermissionsProvider.requestIDFAPermision(withDelay: 1.0) { isGranted in
                if isGranted {
                    print("User granted for personalized ads.")
                }
            }
        }
    }
}

Notes:

  1. Keep in mind there's an option Allow Apps to Request to Track in system's Settings app, and if it's off, requestTrackingAuthorization will return .denied immediately.
  2. Don't forget to add AdSupport.framework & AppTrackingTransparency.framework in your_project.xcodeproj > targets > Frameworks, Libraries and Embedded Content

Solution 9:[9]

The call to the authorization method ATTrackingManager.requestTrackingAuthorization must be placed in the viewDidAppear(_:) method of the main controller or even further (run later), i.e. can be tied to a button

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 Serg Smyk
Solution 2 Abhishek Aryan
Solution 3 Di_Nerd
Solution 4 Tritmm
Solution 5
Solution 6 Oscar
Solution 7
Solution 8
Solution 9