'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:
- Keep in mind there's an option
Allow Apps to Request to Trackin system's Settings app, and if it's off, requestTrackingAuthorization will return .denied immediately. - Don't forget to add
AdSupport.framework&AppTrackingTransparency.frameworkinyour_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 |
