iOS 9: How to detect when user said 'Don't Allow' to the push notification request? [duplicate]
Asked Answered
R

7

21

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.

Restore answered 1/2, 2016 at 8:56 Comment(5)
@iThink But this alert view is propagated by the operating system. I don't think I have access to this alert view delegate method.Restore
Just check the out the answer on the following stack overflow link hope this will work . . Show AnswerHospitalize
#26052450Arthrospore
in iOS 10 if you use the UserNotifications framework then you can find out if the user clicked yes/no using a callback. See hereVeronikaveronike
Did you find any proper solution for this ?Harneen
V
28

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

Vookles answered 1/2, 2016 at 9:6 Comment(1)
You saved my sanity. Thanks a lot. developer.apple.com/documentation/usernotifications/…Yvoneyvonne
B
22

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().

Burgle answered 21/7, 2016 at 6:47 Comment(2)
Hey Boliva. This is such a great answer. Do you happen to know if it's defined behaviour we could trust for future iOS'es? Or it is a hack?Trefor
Hi @RoiMulia. You shouldn't actually trust this for future iOS releases as UIUserNotifications has been deprecated in favor of the UserNotifications framework. I need to update this code to reflect that.Burgle
K
5

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.

Kerns answered 1/2, 2016 at 9:4 Comment(0)
M
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!

Muss answered 1/2, 2016 at 11:0 Comment(2)
You should use this string constant UIApplicationOpenSettingsURLString instead of @"app-settings"Syriac
Thank you...this helped me a lot..!Jacelynjacenta
S
1

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];
    }
}
Singly answered 26/5, 2016 at 13:56 Comment(0)
S
1

Use:

[[UIApplication sharedApplication] isRegisteredForRemoteNotifications];

and NSUserDefaults. Store a key (e.g HasSeenSystemPushNotification) to true when the system dialog for push notifications is presented.

Then you can check the NSUD key and the isRegisteredForRemoteNotifications bool to see whether it has been presented/accepted and do your work accordingly.

Selfpity answered 22/3, 2017 at 18:42 Comment(3)
that won't work because the user could quit the app when the system dialog is shown, right?Zambrano
the system dialog for push notificaions can only be shown once I believeBoneyard
I'm sorry, you are absolutely right. This is not true of other system dialogs though. Thanks for following up.Zambrano
C
0

Check by this method:-

[[UIApplication sharedApplication] isRegisteredForRemoteNotifications];

But if your app supports less than iOS 8 version then you have to do the checking like this:-

UIRemoteNotificationType types = [[UIApplication sharedApplication] enabledRemoteNotificationTypes];
if (types == UIRemoteNotificationTypeNone) 
{ 
  //notification is not enabled by user

}
Crine answered 1/2, 2016 at 8:59 Comment(1)
I need to get a callback when the user taps on 'Don't allow'. I can't simply call [[UIApplication sharedApplication] isRegisteredForRemoteNotifications];Restore

© 2022 - 2024 — McMap. All rights reserved.