Callback Method if user declines Push Notification Prompt?
Solution 1
In iOS 7, when the system's push notification prompt appears, the app becomes inactive and UIApplicationWillResignActiveNotification fires. Similarly when the user responds to the prompt (pressing either Yes or No), the app becomes active again and UIApplicationDidBecomeActiveNotification fires.
So you can listen for this notification, and then hide your loading screen.
Note: While the prompt is displayed, the Home button, Notification Center, and Control Center are disabled so they cannot trigger a false-positive UIApplicationDidBecomeActiveNotification. However if the user presses Lock button it will trigger UIApplicationDidBecomeActiveNotification.
Solution 2
You can always get current allowed notification types from:
UIRemoteNotificationType notificationTypes = [[UIApplication sharedApplication] enabledRemoteNotificationTypes];
Keep in mind user can also disable notification in phone settings.
If you check that on didRegisterForRemoteNotificationsWithDeviceToken you should see if types you asked for are enabled.
Solution 3
Some of the answers here are not relevant anymore, or are more complicated than it should be, since UserNotifications framework and iOS 10 you can easily get this data like so:
let center = UNUserNotificationCenter.current()
// Request permission to display alerts and play sounds.
center.requestAuthorization(options: [.alert, .sound])
{ (granted, error) in
// Enable or disable features based on authorization.
}
Solution 4
Couldn't you just do the following:
- (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings {
BOOL pushEnabled = notificationSettings.types & UIUserNotificationTypeAlert;
}
This method should be the callback to that push notifications prompt, and from there, you can check the bitmask to see if push notifications were enabled or not.
Solution 5
Here's how I did it in Swift 3. They key here is to keep track of the application's lifecycle state internally. When the push prompt is presented, the application resigns active, but does not enter the background. This is all in my AppDelegate.swift.
This is a really big hack and is not recommended in production. Apple could change the way these alerts are presented and this could break at any time. This was tested using various iPhones and iPads running iOS 9 and 10.
/// An internal value used to track application lifecycle state
enum ApplicationLifecycleState {
case willResignActive
case didEnterBackground
case willEnterForeground
case didBecomeActive
case unknown
}
/// This is used purely for tracking the application lifecycle for handling the system push notification alert
var internalLifecycleState: ApplicationLifecycleState = .unknown {
didSet {
// If we're not in the middle of asking for push permissions, none of the below applies, just bail out here
if !isAskingForPushPermissions { return }
// WARNING: Application lifecycle trickery ahead
// The normal application lifecycle calls for backgrounding are as follows:
// applicationWillResignActive -> applicationDidEnterBackground -> applicationWillEnterForeground -> applicationDidBecomeActive
// However, when the system push notification alert is presented, the application resigns active, but does not enter the background:
// applicationWillResignActive -> [user taps on alert] -> applicationDidBecomeActive
// We can use this discrepancy to our advantage to detect if the user did not allow push permissions
// If applicationDidBecomeActive
// AND the previous state was applicationWillResignActive
// AND the notification types bitmask is 0, we know that the user did not allow push permissions
// User denied permissions
if internalLifecycleState == .didBecomeActive
&& oldValue == .willResignActive
&& UIApplication.shared.currentUserNotificationSettings?.types.rawValue == 0 {
// We're done
firePushCompletionBlockAndCleanup(registered: false)
} else {
// The state below can only be entered on iOS 10 devices.
// If the user backgrounds the app while the system alert is being shown,
// when the app is foregrounded the alert will dismiss itself without user interaction.
// This is the equivalent of the user denying push permissions.
// On iOS versions below 10, the user cannot background the app while a system alert is being shown.
if #available(iOS 10, *), internalLifecycleState == .didBecomeActive {
firePushCompletionBlockAndCleanup(registered: false)
}
}
}
}
/// Used internally to track if the system push notification alert is currently being presented
var isAskingForPushPermissions = false
typealias PushNotificationRegistrationCompletionBlock = ((_ registered: Bool) -> Void)
// ...
func applicationWillResignActive(_ application: UIApplication) {
internalLifecycleState = .willResignActive
}
func applicationDidEnterBackground(_ application: UIApplication) {
internalLifecycleState = .didEnterBackground
}
func applicationWillEnterForeground(_ application: UIApplication) {
internalLifecycleState = .willEnterForeground
}
func applicationDidBecomeActive(_ application: UIApplication) {
internalLifecycleState = .didBecomeActive
}
// ...
func setupPushNotifications(_ application: UIApplication = UIApplication.shared, completion: @escaping PushNotificationRegistrationCompletionBlock) {
isAskingForPushPermissions = true
pushCompletionBlock = completion
let settings = UIUserNotificationSettings(types: [.alert, .sound, .badge], categories: nil)
application.registerUserNotificationSettings(settings)
application.registerForRemoteNotifications()
}
fileprivate func firePushCompletionBlockAndCleanup(registered: Bool) {
pushCompletionBlock?(registered)
pushCompletionBlock = nil
isAskingForPushPermissions = false
}
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
// application:didRegisterForRemoteNotificationsWithDeviceToken may be called more than once (once for each notification type)
// By checking that the notification types bitmask is greater than 0, we can find the final time this is called (after the user actually tapped "allow")
// If the user denied push permissions, this function is never called with a positive notification type bitmask value
if UIApplication.shared.currentUserNotificationSettings?.types.rawValue ?? 0 > 0 {
firePushCompletionBlockAndCleanup(registered: true)
}
}
func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
print("Failed to register for notifications with error: " + error.localizedDescription)
firePushCompletionBlockAndCleanup(registered: false)
}
Usage:
appDelegate.setupPushNotifications(completion: { [weak self] (registered) in
// If registered is false, the user denied permissions
})
![最白目](https://i.stack.imgur.com/HMtxo.png?s=256&g=1)
Comments
-
最白目 over 4 years
My problem is I want to show a loading screen for the initial Push Notification Prompt "The app wants to send you push notifications."
So if the user hits
yes
I can proceed and start the app in the then invoked delegate methods:- (void)application:(UIApplication*)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken { [self hideLoadingScreen]; } - (void)application:(UIApplication*)application didFailToRegisterForRemoteNotificationsWithError:(NSError*)error { [self hideLoadingScreen]; }
However if the user hits
no
, none of these methods get called, which makes sense. My question is, is there a different delegate method that gets fired if he declines?My problem is if
no
is selected, the loading screens never disappear. So I somehow need to know when the user is done with the selection. -
最白目 over 10 yearsmy problem is that didRegisterForRemoteNotificationsWithDeviceToken and didFailToRegisterForRemoteNotificationsWithError never get called if the user hits no on the prompt.
-
最白目 over 10 yearshmm but when to access the BOOL? there seems to be no callback fired if the user doesnt allow Push Notifications.
-
Puneet Sharma over 10 yearsYou want to disappear the loading screen right? If I am guessing right then you show the user a loading screen at the launch. Then app asks for allowing pushnotification. If allowed then show a different screen, if disallowed then show different screen. Right?
-
最白目 over 10 yearswell, it´s the same screen on allow and disallow, but basically you are right. as soon as the user presses yes or no, the current loading screen should disappear.
-
Puneet Sharma over 10 yearsOne way I can think of is using NSTimer but that wont be user friendly. Another way that may work is intercepting every touch in UIWindow. Here this may help youhttp://stackoverflow.com/questions/2003201/observing-pinch-multi-touch-gestures-in-a-uitableview/2003781#2003781
-
最白目 over 10 yearsso you have the same impression as me, that there are no callbacks fired if the user declines push notifications?
-
Puneet Sharma over 10 yearsYes that seems true. That Alert View is generated by ios system, independent of the app and I dont think Apple has provided any APIs to access it.
-
最白目 over 10 yearsthat´s awful. Luckily we agreed on not showing a loading screen while the pop up appears now.
-
Puneet Sharma over 10 yearsGood for you. Enjoy your day!!
-
Mark Edington over 10 yearsUpvoted, because despite the fact this doesn't provide a direct answer to the original question it does provide a mention of something quite useful that isn't even mentioned in the documentation of
registerForRemoteNotificationTypes
! -
Isaac Overacker over 9 yearsThe API for this has changed in the iOS 8 SDK. On iOS >= 8.0, you have to check
[[UIApplication sharedApplication] currentUserNotificationSettings].categories
. -
ekscrypto over 7 yearsI can confirm that this behaviour is still current in iOS 10
-
gabriel_vincent about 5 yearsI can confirm that this behaviour is still current in iOS 12
-
Rizwan Ahmed almost 4 yearsI can confirm that this behaviour is still current in iOS 14 beta 2