I’m trying to implement the ATTrackingManager.requestTrackingAuthorization in my Swift app. I saw the tracking usage description appear once, but haven’t seen it since. Based on another post, I think that since I’m changing the State
variables after an .onAppear()
the message is either not being displayed or displaying and then being removed/overwritten.
I’ve set Privacy - Tracking Usage Description
in the info.plist
I tried to put the ATT call on an .onAppear
in the forest group that is displayed. One an Apple technical forum, I saw that if the result returned is "Not Determined", call ATTrackingManager.requestTrackingAuthorization
a second time.
It seems like I should be calling the requestTrackingAuthorization
from a different place than onAppear
and I’m making the process way to complicated.
What I need to happen:
I need the ATT check to happen every time the user opens the app to support new installs and existing installs and the message to display when the value is .notDetermined
. The user needs to be able to make a selection (Track/Not Track) before entering any authentication data AuthentiationView()
or as the user is already authenticated and presented with SeriesTabs(selection: $selection)
Code showing the .onAppear usage and the ContentView
import SwiftUI
import UIKit
import Firebase
@main
struct MyToyBoxApp: App {
@StateObject private var modelData = ModelData()
init() {
FirebaseApp.configure()
}
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(modelData) // app data
.environmentObject(AuthenticationState.shared) // firebase
}
}
}
import SwiftUI
import AppTrackingTransparency
enum Tab {
case photos
case list
}
struct ContentView: View {
@EnvironmentObject var authState: AuthenticationState
@State private var selection: Tab = .photos
var body: some View {
Group {
if authState.loggedInUser != nil {
SeriesTabs(selection: $selection)
} else {
AuthentiationView()
}
}
.onAppear() {
if let status = determineDataTrackingStatus() {
if status == .notDetermined {
print("First call status (status)")
let statusSecondCall = determineDataTrackingStatus()
print ("Second call status (String(describing: statusSecondCall))")
}
}
}
}
func determineDataTrackingStatus() -> ATTrackingManager.AuthorizationStatus? {
var authorizationStatus: ATTrackingManager.AuthorizationStatus?
if #available(iOS 14, *) {
ATTrackingManager.requestTrackingAuthorization { status in
authorizationStatus = status
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 {
// do nothing
}
return (authorizationStatus)
}
}
I’ve also checked this post, but I’m not sure where ApplicationDidBecomeActive needs to be set or what its overriding as there is no override mentioned.
2
Answers
I used a combination of
.onChange
methods across a few different fields to allow all the messages to appear.Simple flow:
.authorized
localizedDescription
View Flow
ContentView
- if authenticated, shows the user's dataSeriesView
view. If not authenticated, shows theAuthenticationView
AuthenticationView
allows authentication by email/password or Apple IDHere are a few examples of what I ended up using:
On the SeriesView, I needed the following
onChange
of thescenePhase
watching for the.active
phaseThen for the appleID auth check:
The next bit of code seemed a bit sloppy, but worked. When the
email
field changes and/or was active, I checked the tracking authorizationNext is an example of the
checkTrackingAuthStatus
. All 3 calls were similar with slight variation.@Om, as you noted above, the trackingState trackingStatus call which didn't need the
asynchAfter call
There is no need to check
status
on appearATTTrackingDialougue
should called without checking status.