35

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?

Iulian Onofrei
  • 9,188
  • 10
  • 67
  • 113
freezing_
  • 984
  • 1
  • 9
  • 13
  • 1
    It doesn't work for me on a real device either, only on the simulator (even if app tracking is activated). Usually the activated apps are listed below the general app tracking mode, but the listed apps are missing on the physical device. It is now unclear how this should be dealt with in productive operation. Hope for a quick fix from Apple! – MosTwanTedT Feb 17 '21 at 17:23

12 Answers12

36

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.

Privacy Setting
dlackty
  • 1,861
  • 14
  • 17
  • 20
    I have it active, I reinstalled the app and it still doesn't present the alert. – Iulian Onofrei Jan 06 '21 at 13:43
  • @lulianOnofrei did you get this to work in the end. I'm having the same problem with 14.4 –  Feb 11 '21 at 11:59
  • I have the same problem. The alert came up once and just to play around I clicked "Ask App not to Track" and now running the app again always returns "denied" without showing the alert, even if I remove the app or change the Tracking switch shown above in settings. This is on iOS 14.4 on a real device. How can we test and debug if there's no way to get back to the original state, where the alert is shown? – Bbx Feb 22 '21 at 20:59
  • 1
    Ah I finally got the alert back. 1) With app installed, Settings > Disc Drop (my game) > Allow Tracking switch ON. 2) Remove the app 3) Make sure Allow Apps to Request to Track (see above pic) is ON. 3) Install app again and TrackingAuthorizationStatus is "NotDetermined initially and alert will show. – Bbx Feb 22 '21 at 21:16
  • @Bbx I have tried your step in Debug mode and it doesn't work. – Houman Apr 29 '21 at 07:43
  • @Houman Yea, I can't reproduce the original problem now. I'm using Xcode 12.4 and a real iOS 14.4 device and I always get the alert if I remove the app each time. – Bbx Apr 30 '21 at 09:08
  • Do you know when will TrackingAuthorizationStatus be "restricted"? – Deepjyot Kaur Jun 16 '21 at 10:16
16

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

Serg Smyk
  • 613
  • 5
  • 11
5

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.

Abhishek Aryan
  • 19,936
  • 8
  • 46
  • 65
4

For those still having this issue. My problem was calling the permission method in viewDidLoad seems to be too early. I just changed my code to call is slightly later and it works. I hope this helps.

Lucas Dahl
  • 722
  • 1
  • 5
  • 20
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)
                    
                }
Di Nerd Apps
  • 770
  • 8
  • 15
2

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
}
Cyril Cermak
  • 191
  • 2
  • 4
2

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")
                }
            }
    }
}
        
Elias
  • 21
  • 3
1

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)
Tritmm
  • 1,972
  • 18
  • 14
0

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

Oscar
  • 1,899
  • 1
  • 22
  • 32
0

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
Coder ACJHP
  • 1,940
  • 1
  • 20
  • 34
-1

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

Eduard
  • 1
  • 2
-3

It seems you needs to use iOS 14.4.

I assume you have set allow / deny once before. I was facing same problem, the asking permission alert never appear again even by uninstalling the app when I was trying it with iOS 14.0. But when I use iOS to 14.4, I could reset setting and see the asking alert again by uninstall my app.

hiroshi046
  • 33
  • 5