ATTrackingManager.requestTrackingAuthorization stopped working on ios 15. Application rejected from Apple.
According to the discussion in Apple Developer Forum, you need to add delay for about one second when calling requestTrackingAuthorization.
https://developer.apple.com/forums/thread/690607
Example:
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0, execute: {
ATTrackingManager.requestTrackingAuthorization(completionHandler: { status in
// Tracking authorization completed. Start loading ads here.
// loadAd()
})
})
P.S.
Also if you have requesting push notification permission, firstly you need request push notification then request tracking authorization with a delay =>
private func requestPushNotificationPermission() {
let center = UNUserNotificationCenter.current()
UNUserNotificationCenter.current().delegate = self
center.requestAuthorization(options: [.sound, .alert, .badge], completionHandler: { (granted, error) in
if #available(iOS 14.0, *) {
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0, execute: {
ATTrackingManager.requestTrackingAuthorization(completionHandler: { status in
// Tracking authorization completed. Start loading ads here.
// loadAd()
})
})
}})
UIApplication.shared.registerForRemoteNotifications()
}
The problem has been solved, just call it in applicationDidBecomeActive:
https://developer.apple.com/forums/thread/690762
Follow by apple doc:
Calls to the API only prompt when the application state is UIApplicationStateActive.
So, we need to call ATTrackingManager.requestTrackingAuthorization on
applicationDidBecomeActive of AppDelegate.
But If you're using scenes (see Scenes), UIKit will not call this method. Use sceneDidBecomeActive(_:) instead to restart any tasks or refresh your app’s user interface. UIKit posts a didBecomeActiveNotification regardless of whether your app uses scenes.
So, my approach is to register on addObserver on didFinishLaunchingWithOptions such as:
NotificationCenter.default.addObserver(self, selector: #selector(handleRequestEvent), name: UIApplication.didBecomeActiveNotification, object: nil)
on handleRequestEvent:
requestPermission() // func call ATTrackingManager.requestTrackingAuthorization NotificationCenter.default.removeObserver(self, name: UIApplication.didBecomeActiveNotification, object: nil)
Hope this helps. It's work for me.
Make sure your iPhone's Settings -> Privacy -> Tracking is enabled. Otherwise, it won't prompt for request Aurthorization.
Related
I'm adding Firebase Cloud Messaging in an iOS app (iOS 15.2.1 on a 7th Gen iPad), and I am able to get it to work, but have taken a step that the Firebase docs don't specify, and I don't understand why it's working and why it doesn't work when I don't do this.
First, I'm following these Firebase docs. These docs make no mention of needing to have an application:didRegisterForRemoteNotificationsWithDeviceToken method in the App Delegate. However, if I don't have this method present, I consistently get this error spewed to the console whenever I try to access the token with Messaging.messaging().token:
2022-01-31 22:00:57.448319-0800 authNavFirebaseUISample[4755:1363959] 8.10.0 - [Firebase/Messaging][I-FCM002022] APNS device token not set before retrieving FCM Token for Sender ID 'XXXXXXXXXXXX'. Notifications to this FCM Token will not be delivered over APNS.Be sure to re-retrieve the FCM token once the APNS device token is set.
When I do add in this method, I no longer get that error when accessing the token via Messaging.messaging().token. Furthermore, when this method is present, I can successfully send test notifications via the Notification Composer.
What's particularly strange about all of this is that the actual application:didRegisterForRemoteNotificationsWithDeviceToken is never actually invoked. However, its presence is required and without it, I get that error spew and Notification Composer doesn't work.
I've added relevant code snippets below. Any ideas? Thanks!
App Delegate
class AppDelegate: NSObject, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
FirebaseApp.configure()
NotificationHelpers.setupRemoteNotifications(application)
return true
}
// NOTE: Why is this needed?!?!
// If this method isn't present, each time we try to retrieve the FCM (Firebase Cloud
// Messaging) token, we get:
// APNS device token not set before retrieving FCM Token for Sender ID 'XXXXXXXXXXXX'. Notifications to
// this FCM Token will not be delivered over APNS.Be sure to re-retrieve the FCM token once the APNS
// device token is set.
// Adding this method prevents that. However, the method never actually gets executed. (Try putting a
// print statement in there, it won't print.) But if you remove the method, then we get the above error.
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
}
func application(
_ application: UIApplication,
configurationForConnecting connectingSceneSession: UISceneSession,
options: UIScene.ConnectionOptions
) -> UISceneConfiguration {
let sceneConfig = UISceneConfiguration(name: nil, sessionRole: connectingSceneSession.role)
sceneConfig.delegateClass = MySceneDelegate.self
return sceneConfig
}
}
NotificationHelpers
class NotificationHelpers {
static func setupRemoteNotifications(_ application: UIApplication) -> Void {
let delegate = NotificationsDelegate()
// For iOS 10 and above display notification (sent via APNS)
UNUserNotificationCenter.current().delegate = delegate
let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
UNUserNotificationCenter.current().requestAuthorization(
options: authOptions,
completionHandler: { _, _ in }
)
application.registerForRemoteNotifications()
Messaging.messaging().delegate = delegate
}
}
Token accessing code (invoked from button press in app)
Button("retrieve FCM") {
// Get token here, but really can be from anywhere
Messaging.messaging().token { token, error in
if let error = error {
print("Error fetching FCM registration token: \(error)")
} else if let token = token {
print("FCM registration token: \(token)")
}
}
}
After reading about this extensively, I cannot find the solution. I am sending cloud messages from Firebase to my ios app, but the method didReceiveRemoteNotification() is not being called at all, not when the app is in foreground and not in background. I tried sending from the firebase console, and also from postman using topics, and both don't work. I have integrated Firebase in the app, and uploaded certificate to Firebase, and I get no errors in the console. Just the notification isn't coming? I also added the capability of cloud messages.
This is what I do (from the Firebase tutorial) in appDelegate:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
FirebaseApp.configure()
if #available(iOS 10.0, *) {
// For iOS 10 display notification (sent via APNS)
UNUserNotificationCenter.current().delegate = self
let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
UNUserNotificationCenter.current().requestAuthorization(
options: authOptions,
completionHandler: {_, _ in })
} else {
let settings: UIUserNotificationSettings =
UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil)
application.registerUserNotificationSettings(settings)
}
application.registerForRemoteNotifications()
Messaging.messaging().delegate = self
return true
}
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any]) {
// If you are receiving a notification message while your app is in the background,
// this callback will not be fired till the user taps on the notification launching the application.
// TODO: Handle data of notification
// With swizzling disabled you must let Messaging know about the message, for Analytics
// Messaging.messaging().appDidReceiveMessage(userInfo)
// Print message ID.
if let messageID = userInfo[gcmMessageIDKey] {
print("Message ID: \(messageID)")
}
// Print full message.
print(userInfo)
}
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any],
fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
// If you are receiving a notification message while your app is in the background,
// this callback will not be fired till the user taps on the notification launching the application.
// TODO: Handle data of notification
// With swizzling disabled you must let Messaging know about the message, for Analytics
// Messaging.messaging().appDidReceiveMessage(userInfo)
// Print message ID.
if let messageID = userInfo[gcmMessageIDKey] {
print("Message ID: \(messageID)")
}
// Print full message.
print(userInfo)
completionHandler(UIBackgroundFetchResult.newData)
}
Disable Method swizzling and everything should work fine.
According to the documentation https://firebase.google.com/docs/cloud-messaging/ios/client#token-swizzle-disabled
If you have disabled method swizzling, or you are building a SwiftUI app, you'll need to explicitly map your APNs token to the FCM registration token. Implement the application(_:didRegisterForRemoteNotificationsWithDeviceToken:) method to retrieve the APNs token, and then set Messaging's apnsToken property
It also mentions here https://firebase.google.com/docs/cloud-messaging/ios/receive#handle_messages_with_method_swizzling_disabled
By default, if you assign your app's app delegate class to the UNUserNotificationCenter and Messaging delegate properties, FCM will swizzle your app delegate class to automatically associate your FCM token with the device's APNs token and pass notification-received events to Analytics. If you explicitly disable method swizzling, if you are building a SwiftUI app, or if you use a separate class for either delegate, you will need to perform both of these tasks manually.
Mapping apnToken and deviceToken is to be done at various places thus it's advised to read the documentation completely and apply the necessary changes.
I've spent hours now trying to figure out why didRegisterForRemoteNotificationsWithDeviceToken is not being called.
It worked before. I didn't touch it in weeks. Now stopped working.
Here's my setup:
Got didRegisterForRemoteNotificationsWithDeviceToken sitting in SceneDelegate
Have declared SceneDelegate to be the UNUserNotificationCenterDelegate
I'm setting UNUserNotificationCenter.current().delegate = self in func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions)
I'm calling UNUserNotificationCenter.current().requestAuthorization { granted, error in... from one of the UIViewControllers in my App, specifically in viewDidLoad of that Controller - I get the Authorization Pop-up and when accepting I get a true back for granted
Within UNUserNotificationCenter.current().getNotificationSettings { settings in ... a check for settings.authorizationStatus == .authorized returns true
Push Notifications are added as a capability for the App in "Signing & Capabilities" and the entitlement file has been created. I've even already deleted and re-created the entitlement file - just to be sure...
I've made sure to run the app on a real device (both via Xcode as well as via TestFlight) - so the delegate not being called is not related to the App running in the Simulator.
Have checked if didFailToRegisterForRemoteNotificationsWithError gets called instead at least - but it doesn't.
I have tried with a second physical device which I have never used for testing before.
I've even checked the status of the APNS Sandbox here: https://developer.apple.com/system-status/
The didReceive delegate gets called though if I'm sending a test notification to simulator via terminal command xcrun simctl push... and I'm getting the notification.
Provisioning profile is managed by Xcode and the specified AppID is configured for Push Notifications. Certificates are set up in apple developer account.
Here's how I'm requesting Authorization from the user within viewDidLoad of one of my Apps UIViewControlles
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { granted, error in
DispatchQueue.main.async {
if (granted) {
//self.processInitialAPNSRegistration()
UIApplication.shared.registerForRemoteNotifications()
UserDefaults.standard.set(true, forKey: "pushNotificationsEnabled")
}
print("permission granted?: \(granted)")
}
}
And here's the didRegisterForRemoteNotificationsWithDeviceToken delegate method sitting inside SceneDelegate. Note: I'm also setting UNUserNotificationCenter.current().delegate = self in scene willConnectTo and declaredSceneDelegateto implement theUNUserNotificationCenterDelegate` protocol.
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
let token = deviceToken.map { String(format:"%02.2hhx", $0) }.joined()
print("didRegisterForRemoteNotificationsWithDeviceToken GOT CALLED - APNS TOKEN IS: \(token)")
self.apiService.setAPNSToken(apnsToken: token, completion: {result in
switch result {
case .success(let resultString):
DispatchQueue.main.async {
UserDefaults.standard.set(token, forKey: "apnsToken")
print(resultString, " TOKEN IS: \(token)")
}
case .failure(let error):
print("AN ERROR OCCURED: \(error.localizedDescription)")
}
})
}
Whatever I do, didRegisterForRemoteNotificationsWithDeviceToken is not getting triggered. I'm running out of ideas on what is going wrong.
Where is the error? How can I get didRegisterForRemoteNotificationsWithDeviceToken to execute again?
I think you have gone wrong in the second step didRegisterForRemoteNotificationsWithDeviceToken, didFailToRegisterForRemoteNotificationsWithError delegates are available in UIApplicationDelegate not in UNUserNotificationCenterDelegate
Add UIApplicationDelegate to your SceneDelegate class and in willConnectTo function set the delegate as:
UIApplication.shared.delegate = self
I hope this works... let me know if you still see an issue.
Have you made sure that you use the right provisioning profile which is using a push notification enabled AppId? This one is missing in your steps.
As I am not eligible to put comments yet, posting this in answer.
I'm creating a VOIP App.
Most of the logic is similar to the Raywenderlich Tutorial.
My issue is that after accepting the call, the CXCallController is not on top of the App, but next to it in the "task manager":
The problem with that is, that once you have accepted the call, the CXCallController is not the top most controller, but the actual App.
So you can't tell that the call is taking place.
And to perform actions regarding the call - for example hang up, hold, mute you would have to go through the task manager to open the CXCallController. Not very user friendly.
How do I bring the CXCallController to the front after the user has accepted the call?
Provider delegate called after the user has answered the call:
func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
guard let call = callManager.getCall(with: action.callUUID) else {
action.fail()
return
}
configureAudioSession()
self.incommingCall = call
action.fulfill()
}
Callback after the audio session has been created:
func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) {
incommingCall?.answerCall(with: audioSession) { success in
if success {
self.incommingCall?.startAudio()
}
}
}
Start call logic using TokBox VOIP Service:
func answerCall(with audioSession: AVAudioSession, completion: ((_ success: Bool) -> Void)?) {
OTAudioDeviceManager.setAudioDevice(OTDefaultAudioDevice.sharedInstance(with: audioSession))
if session == nil {
session = OTSession(apiKey: self.apiKey, sessionId: self.sessionID, delegate: self)
}
answerCallCompletion = completion
var error: OTError?
hasStartedConnecting = true
session?.connect(withToken: self.token, error: &error)
if error != nil {
CoreServices.catchError(error: error, at: #function)
}
}
The call itself works fine. Two parties are able to communicate, start and end calls. The only issue is the described CXCallController behavior.
Is this expected behavior? Or how do I bring the CXCallController to the front?
Help is very appreciated.
This is the expected behaviour of the callkit. Callkit allows us to answer or decline the call when the app is receiving the incoming call. After answering the call, the app has to manage all the actions.
when the phone is locked, receive the incoming call, CXCallController will be visible, after answering the call, still you can see the CXCallController in the lock mode. After unlocking the phone, CXCallController will be invisible, the app has to manage the hangup, mute or hold options.
when the phone is not locked, receive the incoming call, CXCallController will be visible, after answering the call, CXCallController will be invisible, the app has to manage the hangup, mute or hold options.
I'm making a framework that needs to do stuff when my apple watch is entering background and foreground.
I'm looking for an equivalent of this iOS code for the Apple watch since UIApplication is not present in UIKit anymore :
let notificationCenter = NSNotificationCenter.defaultCenter()
notificationCenter.addObserver(self, selector: "applicationDidEnterBackground", name: UIApplicationDidEnterBackgroundNotification, object: nil)
any help would be nice
As of watchOS 7 the following have been added:
WKExtension.applicationDidBecomeActiveNotification
WKExtension.applicationDidEnterBackgroundNotification
WKExtension.applicationDidFinishLaunchingNotification
WKExtension.applicationWillEnterForegroundNotification
WKExtension.applicationWillResignActiveNotification
Source: https://developer.apple.com/documentation/WatchKit/wkextension
Appears that the WatchOS equivalent of
let notificationCenter = NSNotificationCenter.defaultCenter()
notificationCenter.addObserver(self, selector: "applicationDidEnterBackground", name: UIApplicationDidEnterBackgroundNotification, object: nil)
is simply
let notificationCenter = NSNotificationCenter.defaultCenter()
notificationCenter.addObserver(self, selector: "applicationDidEnterBackground", name: "UIApplicationDidEnterBackgroundNotification", object: nil)
One just need to replace the emum by its string equivalent
Closest you get is applicationDidBecomeActive and applicationWillResignActive
class ExtensionDelegate: NSObject, WKExtensionDelegate {
func applicationDidFinishLaunching() {
// Perform any final initialization of your application.
}
func applicationDidBecomeActive() {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
func applicationWillResignActive() {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, etc.
}
}