I've tried the solutions in other related questions but none of the answers work for me.
I've got App Tracking Transparency set up but for some reason, the dialogue box for it never shows on screen. This is the code I'm using:
func appTrackingAuthorisation() {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
ATTrackingManager.requestTrackingAuthorization { status in
switch status {
case .authorized:
print("Enable tracking")
case .denied:
print("Deny tracking")
case .restricted:
print("Restrict (denied) tracking")
default:
print("Not determined")
}
}
}
}
In my viewDidLoad() is where I'm calling the function:
override func viewDidLoad() {
super.viewDidLoad()
appTrackingAuthorisation()
checkLocationServices()
}
I also have a location permission dialogue box show and I'm not sure if that being there is causing the ATT to not show up. I've tried putting it in AppDelegate in func applicationDidBecomeActive(_ application: UIApplication) as that's what Apple recommends but it still doesn't work. NSUserTrackingUsageDescription in Info.plist is also set. Allow apps to ask for tracking is also enabled.
Related
In iOS 14, It could display ATT (App Tracking Transparency) dialog when app starts in SwiftUI as follows.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
if #available(iOS 14, *) {
ATTrackingManager.requestTrackingAuthorization(completionHandler: { status in
// loadAd()
})
} else {
// loadAd()
}
return true
}
But, in iOS 15.0, it does not work. Apple document describes as follows.
Calls to the API only prompt when the application state is: UIApplicationStateActive. Calls to the API through an app extension do not prompt.
https://developer.apple.com/documentation/apptrackingtransparency/attrackingmanager/3547037-requesttrackingauthorization
How to display ATT dialog when the app starts in iOS 15 ?
2021/9/28 update
I solved it as follows.
struct HomeView: View {
var body: some View {
VStack {
Text("Hello!")
}.onReceive(NotificationCenter.default.publisher(for: UIApplication.didBecomeActiveNotification)) { _ in
ATTrackingManager.requestTrackingAuthorization(completionHandler: { status in })
}
}
}
Instead of calling app tracking transparency permission in didFinishLaunchingWithOptions call in applicationDidBecomeActive it will solve your issue
In AppDelegate
func applicationDidBecomeActive(_ application: UIApplication) {
requestDataPermission()
}
func requestDataPermission() {
if #available(iOS 14, *) {
ATTrackingManager.requestTrackingAuthorization(completionHandler: { 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 {
//you got permission to track, iOS 14 is not yet installed
}
}
in info.plist
<key>NSUserTrackingUsageDescription</key>
<string>Reason_for_data_tracking</string>
As #donchan already mentioned use the following code:
struct HomeView: View {
var body: some View {
VStack {
Text("Hello!")
}.onReceive(NotificationCenter.default.publisher(for: UIApplication.didBecomeActiveNotification)) { _ in
ATTrackingManager.requestTrackingAuthorization(completionHandler: { status in })
}
}
}
For iOS 15, I faced the same problems and I fixed them by delaying code execution for a second.
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
if #available(iOS 14, *) {
ATTrackingManager.requestTrackingAuthorization(completionHandler: { status in
DispatchQueue.main.async {
self.bannerView.load(GADRequest())
self.interstitial.load(request)
}
})
} else {
// Fallback on earlier versions
self.bannerView.load(GADRequest())
self.interstitial.load(request)
}
}
A very important addition to all the answers above: the ATT dialogue must be invoked once!
If, for example, inside the advertising manager you have repeated calls to the ATT dialog before requesting an advertisement (as it was for previous OS versions), then the dialog WILL NOT be shown! Therefore, the ATT dialogue request must be inserted directly into the view and with a delay of at least 1 second for its unconditional triggering.
If you are writing a SwiftUI app, you can trigger it on your start screen.
struct HomeView: View {
var body: some View {
VStack {
Text("Hello!")
}.onAppear {
ATTrackingManager.requestTrackingAuthorization(completionHandler: { status in })
}
}
}
Do not forget to add the necessary additions to the .plist.
<key>NSUserTrackingUsageDescription</key>
<string>...</string>
Thus, it will run on the simulator or real device.
I don't know can I use this functionality in my UI tests on iOS, but I try it, an have problem with this.
In my UI tests I can choose Allow tracking for my app or I can decline tracking, but after all these actions, I want checkout status IDFA via ATTrackingManager.AuthorizationStatus, but this method always returns notDetermined. If I go to Settings > Privacy > Tracking, here I see that settings applied correctly (switch Allow App To Request To Track is on and switch for my app in right state (on or off)).
I don't have any idea why I recieve wrong AuthorizationStatus.
Here is my code in my XCTestCase:
import AppTrackingTransparency
enum TrackingStatus {
case authorized
case denied
case notDetermined
}
func map(_ status: ATTrackingManager.AuthorizationStatus) -> TrackingStatus {
switch ATTrackingManager.trackingAuthorizationStatus {
case .notDetermined:
return .notDetermined
case .authorized:
return .authorized
default:
return .denied
}
}
func advertisingTrackingStatusCheckout(status: TrackingStatus) {
print("IDFA status: \(ATTrackingManager.trackingAuthorizationStatus)")
var currentTrackingStatus: TrackingStatus {
return map(ATTrackingManager.trackingAuthorizationStatus)
}
guard currentTrackingStatus == status else {
XCTFail("IDFA status: \(currentTrackingStatus), expected: \(status)")
return
}
}
After settings IDFA status in my UI test, i call this method, ex. advertisingTrackingStatusCheckout(status: TrackingStatus.denied)
But it always returns notDetermined.
It behaviors have only one exception: If I manually set switch Allow App To Request To Track to off-state, calling the ATTrackingManager.trackingAuthorizationStatus will returns denied.
Delete the App, And call your function in sceneDidBecomeActive with delay. So once your app become active then it will shown. Am facing the same issue now its resolved. Like this
func sceneDidBecomeActive(_ scene: UIScene) {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
self.requestPermission()
}
}
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 Tracking Permission")
// Now that we are authorized we can get the IDFA
case .denied:
// Tracking authorization dialog was
// shown and permission is denied
print("Denied Tracking Permission")
case .notDetermined:
// Tracking authorization dialog has not been shown
print("Not Determined Tracking Permission")
case .restricted:
print("Restricted Tracking Permission")
#unknown default:
print("Unknown Tracking Permission")
}
}
} else {
// Fallback on earlier versions
}
}
I'm trying to implement the App Tracking Transparency framework, and I'm stuck, how do I load non-personalised content when the user denies the prompt.
if #available(iOS 14.5, *) {
ATTrackingManager.requestTrackingAuthorization { (status) in
switch status {
case .denied:
// What do I do here?
//GADMobileAds.sharedInstance().start(completionHandler: nil)
case .restricted, .notDetermined, .authorized:
GADMobileAds.sharedInstance().start(completionHandler: nil)
#unknown default: break
}
}
} else {
GADMobileAds.sharedInstance().start(completionHandler: nil)
}
You don't need to do anything different. If the user denies tracking then the ad framework will simply receive 0000 for the IDFA. This prevents them from identifying the user and tracking them or providing personalised ads.
if #available(iOS 14.5, *) {
ATTrackingManager.requestTrackingAuthorization { (status) in
ADMobileAds.sharedInstance().start(completionHandler: nil)
}
} else {
GADMobileAds.sharedInstance().start(completionHandler: nil)
}
You could use the .denied status to show an alert that asks them to go into settings and allow it, but don't do that.
I am exploring the MPMediaPickerController for the first time, and I don't see anything when my button is pushed. The MPMediaPickerController.view has a frame after it is presented but it is not visible at all, nor does it appear in the view hierarchy. I am just using a simple fresh POC application with a ViewController and a IBOutlet button constrained to center vertical and horizontal, that calls this function:
func checkMediaAccess() {
let status = MPMediaLibrary.authorizationStatus()
switch status {
case .authorized:
showMediaPicker()
case .notDetermined:
MPMediaLibrary.requestAuthorization() { status in
if status == .authorized {
DispatchQueue.main.async {
self.showMediaPicker()
}
}
}
break
case .denied:
print("Denied")
break
case .restricted:
break
#unknown default:
break
}
}
Which then calls this function on authorized:
func showMediaPicker() {
print("SHOWING")
let picker = MPMediaPickerController(mediaTypes: .music)
picker.allowsPickingMultipleItems = false
picker.popoverPresentationController?.sourceView = pickerButton
picker.showsCloudItems = true
picker.delegate = self
self.present(picker, animated: true, completion: nil)
print(picker.view.frame )
}
"SHOWING" is printed, but again I don't see anything. I don't get anything else printed either.
I have all the info.plist descriptions for Media and Music access in. I also do get the prompt to allow the first time, and again "SHOWING" is printed. I have the delegates implemented, but they are never fired, because I don't see the view.
Nothing happens visually when I tap the button, and I can tap the button again.
What am i missing?
maybe the music app not installed!
I'm trying to set up an on boarding where I'm asking the user for a few permissions including: Location, Notifications and Camera. I have 3 different View Controllers set up, each asking for one of the permissions and explaining why. On each of the view controllers I have a button at the bottom that says "Grant Permission".
When the user clicks the button I want the permission dialogue to pop up, and once the user clicks allow I want to transition to the next view controller.
Here is what I have right now:
class OnboardingStep2:UIViewController{
override func viewDidLoad() {
self.view.backgroundColor = StyleKit.orangeWhite()
}
#IBAction func getPermission(sender: AnyObject) {
dispatch_sync(dispatch_get_main_queue()) {
let locManager = CLLocationManager()
locManager.requestAlwaysAuthorization()
}
if (CLLocationManager.authorizationStatus() == CLAuthorizationStatus.Authorized) {
self.performSegueWithIdentifier("goToStep3", sender: self)
}
}
}
I've tried using dispatch to queue up the tasks, but when using async the permission dialogue pops up and then immediately it closes because the authorization check is run (I'm assuming). Using dispatch_sync, the dialogue is never shown.
What is the best way to do this, I want the permission dialogue to pop up first and once the user clicks allow i want to segue.
Conform to the CLLocationManagerDelegate
Then call this:
Swift 3.0
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
switch status {
case .notDetermined:
manager.requestLocation()
case .authorizedAlways, .authorizedWhenInUse:
// Do your thing here
default:
// Permission denied, do something else
}
}
Swift 2.2
func locationManager(manager: CLLocationManager, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
switch status {
case .NotDetermined:
manager.requestLocation()
case .AuthorizedAlways, .AuthorizedWhenInUse:
// Do your thing here
default:
// Permission denied, do something else
}
}
Swift 5
Implement CLLocationManagerDelegate
and this function:
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
switch CLLocationManager.authorizationStatus() {
case .notDetermined:
// User has not yet made a choice
case .denied:
// User has explicitly denied authorization
case .restricted:
// This application is not authorized to use location services.
case .authorized, .authorizedAlways, .authorizedWhenInUse:
// User has granted authorization
default:
// Other
}
}