iOS 9: How to detect when user said 'Don't Allow' to the push notification request?

28,289

Solution 1

As of iOS 8, the notification registration process changed and moved away from the user having to grant permission to just remote notifications.

You can now technically register for remote notifications without having to get permission from the user. What you do need permission for is the user notification settings (alerts, sounds and badges). These are now generic to both local and remote notifications making the other answers technically incorrect.

You request permission via the -[UIApplication registerUserNotificationSettings:] method on UIApplication and as per the documentation, you get a callback to the -[UIApplicationDelegate application: didRegisterUserNotificationSettings:] delegate method.

In the header, there is a comment saying the following:

// This callback will be made upon calling -[UIApplication registerUserNotificationSettings:]. The settings the user has granted to the application will be passed in as the second argument.

This means that if the user did not grant permissions for notifications (both local and remote) then the second parameter won't contain any values.


-[UIApplication isRegisteredForRemoteNotifications] will just tell you if the applicaiton has actually registered with Apple's push servers and has received a device token:

Return Value
YES if the app is registered for remote notifications and received its device token or NO if registration has not occurred, has failed, or has been denied by the user.

It's worth reading the UIApplication documentation as it has all the info you need.

https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIApplication_Class/#//apple_ref/occ/instm/UIApplication

Solution 2

I just managed to solve this very same issue and would be more than happy to share how I did it (as of iOS 9.3).

In my case, I am using a single custom button to enable notifications with three possible states: default (meaning that the user hasn't been prompted yet to enable notifications), completed (the user has been prompted and agreed to get notifications) and failed (the user rejected the notifications prompt). The button is only enabled while in the default state.

Now, I am not using a single technique here but a combination of a few (albeit related) calls.

The logic is as follows: even if the user rejects the notifications prompt (which only appears once until the user deletes and reinstalls the App), we still register for remote notifications. The process will continue as usual, the device will get registered but the user won't get any notice when a new notification is posted. We can then take advantage of knowing both the current notification settings and whether the user is already registered for remote notifications to know if they have ever been prompted (so the button gets the default status).

This method isn't flawless. If the user initially agrees to get notifications, but later on decides to manually turn them off from Settings, then the button will be set to the default state but, upon activation, won't prompt the user for notifications to be enabled again. But in most cases this shouldn't matter, as this kind of UI is usually shown once during the onboarding/sign up process only.

As for the code itself (Swift 2.2):

func updateButtonStatus() {
    // as currentNotificationSettings() is set to return an optional, even though it always returns a valid value, we use a sane default (.None) as a fallback
    let notificationSettings: UIUserNotificationSettings = UIApplication.sharedApplication().currentUserNotificationSettings() ?? UIUserNotificationSettings(forTypes: [.None], categories: nil)
    if notificationSettings.types == .None {
        if UIApplication.sharedApplication().isRegisteredForRemoteNotifications() {
            // set button status to 'failed'
        } else {
            // set button status to 'default'
        }
    } else {
        // set button status to 'completed'
    }
}

We call this method from our view controller's viewWillAppear(animated) implementation.

At this point a few more other things need to happen: first, whenever the button is touched (which will only occur while in its default state) we must prompt the user to either accept or reject notifications, and we also want our UI to react properly, whatever the user chooses:

@IBAction func notificationsPermissionsButtonTouched(sender: AnyObject) {
    let settings = UIUserNotificationSettings(forTypes: [.Alert, .Badge, .Sound], categories: nil)
    UIApplication.sharedApplication().registerUserNotificationSettings(settings)
}

And then, we need to implement the proper UIApplicationDelegate methods to handle the event. Since there are no global UIApplication notifications for these, we send our own ones:

// AppDelegate.swift

func application(application: UIApplication, didRegisterUserNotificationSettings notificationSettings: UIUserNotificationSettings) {
    application.registerForRemoteNotifications()
    if notificationSettings.types == .None {
        NSNotificationCenter.defaultCenter().postNotificationName("ApplicationDidFailToRegisterUserNotificationSettingsNotification", object: self)
    }
}

func application(application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: NSData) {
    NSNotificationCenter.defaultCenter().postNotificationName("ApplicationDidRegisterForRemoteNotificationsNotification", object: self)
}

Now back to our view controller, we need to handle those notifications. So, in our viewWillAppear(animated) and viewWillDisappear(animated) implementations, we do:

override func viewWillAppear(animated: Bool) {
    super.viewWillAppear(animated)
    NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(PermissionsViewController.applicationDidRegisterForRemoteNotificationsNotification(_:)), name: "ApplicationDidRegisterForRemoteNotificationsNotification", object: nil)
    NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(PermissionsViewController.applicationDidFailToRegisterUserNotificationSettingsNotification(_:)), name: "ApplicationDidFailToRegisterUserNotificationSettingsNotification", object: nil)
    updateButtonStatus()
}

override func viewWillDisappear(animated: Bool) {
    super.viewWillDisappear(animated)
    NSNotificationCenter.defaultCenter().removeObserver(self, name: "ApplicationDidRegisterForRemoteNotificationsNotification", object: nil)
    NSNotificationCenter.defaultCenter().removeObserver(self, name: "ApplicationDidFailToRegisterUserNotificationSettingsNotification", object: nil)
}

And the notification handlers themselves:

func applicationDidRegisterForRemoteNotificationsNotification(notification: NSNotification) {
    let notificationSettings: UIUserNotificationSettings = UIApplication.sharedApplication().currentUserNotificationSettings() ?? UIUserNotificationSettings(forTypes: [.None], categories: nil)
    if notificationSettings.types != .None {
        // set button status to 'completed'
    }
}

func applicationDidFailToRegisterUserNotificationSettingsNotification(notification: NSNotification) {
    // set button status to 'failed'
}

Bonus

What if the user rejected the notifications prompt and we want to have a button to guide them to the Settings panel where they can re-enable it, and make our UI react accordingly? Well, I'm glad you asked.

There is a very little known mechanism to deep-link to your App section inside Settings (it's been there since iOS 8, but I hadn't had the chance to learn about it until a few hours ago). In our settings button touch handler we do this:

@IBAction func settingsButtonTouched(sender: AnyObject) {
    if let settingsURL = NSURL(string: UIApplicationOpenSettingsURLString) {
        UIApplication.sharedApplication().openURL(settingsURL)
    }
}

Since we want to update our UI to reflect whatever changes the user might have made, we add a notification listener for UIApplicationDidBecomeActiveNotification in our viewWillAppear(animated) implementation (don't forget to remove the listener from viewWillDisapper(animated). And finally, from inside the corresponding notification handler method we just call our existing updateButtonStatus().

Solution 3

In your app delegate use this method

- (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings

then you can know if the user gave notification permissions by using

[[UIApplication sharedApplication] isRegisteredForRemoteNotifications]

or use the notificationSettings you receive.

Solution 4

No there is no way to detect push notification from APNS in the application if it is disallowed.

Use this code to check if it is allowed and navigate the app to enable it:

UIRemoteNotificationType types = [[UIApplication sharedApplication] enabledRemoteNotificationTypes];
if (types == UIRemoteNotificationTypeNone)
{
    [[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"app-settings:"]];
}

Hope this helps!

Solution 5

There is a quick and cheap way to do this. iOS9 this delegate method

- (void)didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken

is called once when the dialogue is shown, and then a second time when the user taps on "Ok". Simply add a flag here.

Then, whenever you wish to display the custom "remind user how to enable push" message, simply check the flag and your current notification settings (as detailed by many of the answers above).

- (void)didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {

    self.pushDialogShown = YES;
}

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 

    // not a great place to put this logic. For demonstration purposes ONLY
    if (self.pushDialogueShown && ![self pushMessageEnabled]) {
        [self showPushReminderMessage];
    }
}
Share:
28,289
goelv
Author by

goelv

Updated on March 23, 2020

Comments

  • goelv
    goelv about 4 years

    In iOS 9, is there a system level callback I can read which tells me whether the user has tapped on 'Don't allow' on the push notification request?

    I prompt the user with a custom screen informing them about push notifications and the value it has in my app.

    Custom Tripnary Prompt

    They have two choices, yes or no. If they select Yes, I request the operating system for push notification and they see a pop up like the image below.

    iOS Level system prompt for push notifications

    Now, if the user taps on YES, then there is a function called didRegisterForRemoteNotificationsWithDeviceToken which tells me the this device has been registered for push notifications. I can use this to move ahead to the next screen (and take them into the first screen after signing up)

    However, how do I detect if the user taps on DON'T allow? I need to know that so I can accordingly move the user to the next screen (and take them into the first screen after signing up). The function didFailToRegisterForRemoteNotificationsWithError is not called if the user taps on 'Don't Allow'.

    This question is not a duplicate because the answer accepted for that question is specific to iOS 7, where as my question is specific is iOS 9.