Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Must `registerUserNotificationSettings:` Be called during app launch cycle?

The docs for registerUserNotificationSettings: state:

If your app displays alerts, play sounds, or badges its icon, you must call this method during your launch cycle to request permission to alert the user in these ways.

I was disappointed to read this, as it seems rude for the app to ask for permission to send push notifications before there's a need to. In the app I'm designing, for example, the user must create an account with our online service before there's any reason to send push notifications. And it may be that the user never signs up, just uses the app locally, so there's never any reason to ask. But if I can only ask on app launch, it means the user would have to create an account, quit the app, then launch it again before we could ask. Seems odd.

Is this really necessary? I tried putting the call to registerUserNotificationSettings: into a more relevant part of the app, but then the app never prompted for permission to send push notifications. Is this just a policy for iOS push notifications, or is there some way to have more flexibility as to when to ask for permission to send push notifications?

like image 760
theory Avatar asked Jul 18 '16 12:07

theory


1 Answers

The initial call to -registerForNotifications does not have to be called during the app launch cycle. However, after that first call, it must always be called during the app startup cycle.

There were two issues here, one mine and one from the third-party service I was using for push notifications. My issue was that I was dismissing the view controller that called -registerForNotifications after it returned; I didn't realize that it ran asynchronously. So I added -application:didRegisterUserNotificationSettings to my app delegate. This method is called once the the user has responded to the prompt presented by -registerForNotifications, and I have it post a notification. Before calling -registerForNotifications, I set up a listener for that notification, and only dismiss the view controller once the notification is received. Kind of wish there was a delegate or a block to specify instead, but there's not.

And I suspect that the reason there's not is because iOS really wants you to call -registerForNotifications in application:didFinishLaunchingWithOptions:, so it's natural for the async callback to just be another method in the app delegate. [Deleted bit about having to restart the app to actually get push notifications. Adding:] The other issue was that the third-party service I was using for push notifications, which is still in alpha, was often not sending push notifications. Once that got fixed, they started working as soon as I approved push notifications, whether the prompt came during app startup or later.[/Added]

So here's the complete pattern:

  • In the app delegate's application:didFinishLaunchingWithOptions: method call -registerForNotifications if a user default has been set:

    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
        if NSUserDefaults.standardUserDefaults().boolForKey("registedNotificationSettings") {
            self.registerForNotifications()
        }
        return true
    }
    
  • In the app delegate's -application:didRegisterUserNotificationSettings: method, configure for remote notifications, store the user default value, and post a notification:

    func application(application: UIApplication, didRegisterUserNotificationSettings notificationSettings: UIUserNotificationSettings) {
        if notificationSettings.types != .None {
            application.registerForRemoteNotifications()
        }
        let defaults = NSUserDefaults.standardUserDefaults()
        if !defaults.boolForKey( "registedNotificationSettings") {
            defaults.setBool(true, forKey: "registedNotificationSettings")
            defaults.synchronize()
        }
        NSNotificationCenter.defaultCenter().postNotification(
            NSNotification(name: "RegistedNotificationSettings", object: notificationSettings)
        )
    }
    
  • In my view controller, on a button press, listen for that notification, then request permission to send push notifications:

    @IBAction func okayButtonTapped(sender: AnyObject) {
        NSNotificationCenter.defaultCenter().addObserver(
            self,
            selector: #selector(didRegisterNotificationSettings(_:)),
            name: "RegistedNotificationSettings",
            object: nil
        )
        let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
        appDelegate.registerForNotifications()
    }
    
  • Then of course we'll need the notification method, which dismisses the view controller when permission has been granted, and needs to do something else when it hasn't:

    func didRegisterNotificationSettings(notification: NSNotification) {
        let status = notification.object as! UIUserNotificationSettings
        if status.types != .None {
            // Yay!
            self.done()
            return
        }
    
        // Tell them a little more about it.
        NSLog("User declined push notifications")
        self.done()
    }
    

With this configuration, it all works [Deleted misinformation about -registerForNotifications call time], and does not prompt the user on app startup.

like image 144
theory Avatar answered Nov 15 '22 16:11

theory