user3766930 user3766930 - 10 days ago 5
Swift Question

How can I register user's ios device for receiving push messages from place other than AppDelegate?

Currently in my

appDelegate
in
didFinishLaunchingWithOptions
I'm calling the following methods:

let notificationTypes: UIUserNotificationType = [UIUserNotificationType.alert, UIUserNotificationType.badge, UIUserNotificationType.sound]
let pushNotificationSettings = UIUserNotificationSettings(types: notificationTypes, categories: nil)

application.registerUserNotificationSettings(pushNotificationSettings)
application.registerForRemoteNotifications()


I also have the following methods implemented in
AppDelegate
:

didRegisterForRemoteNotificationsWithDeviceToken

didFailToRegisterForRemoteNotificationsWithError

didReceiveRemoteNotification


When user starts my app for the first time, he sees the popup that ask him for permission about receiving push messages.

I want to postpone showing this popup until user manually chooses specific option in Settings menu.

In Settings menu I have a
UISwitch
and I'm checking the state of it every time user changes it:

func messagesStateChanged(_ sender: UISwitch){
if(sender.isOn){
defaults.set("true", forKey: "popupMessages")
defaults.synchronize()
} else {
defaults.set("false", forKey: "popupMessages")
defaults.synchronize()
}
}


Is there a way of moving the
push
handling from app delegate to the settings menu, so that user sees the popup not immediately after starting the app, but when he selects the
UISwitch
? Also - will that work for future and all
push
functionality works well if I move it to Settings class?

Answer

You can access the UIApplication instance provided to you in didFinishLaunchingWithOptions by accessing UIApplication.shared class property from anywhere in your application, where you have access to UIKit. I usually create a wrapper class that manages notifications, which would allow you to inject UIApplication dependency when you're writing unit tests later.

The register calls should happen when you're activating the switch:

func messagesStateChanged(_ sender: UISwitch){
    if(sender.isOn){
        defaults.set("true", forKey: "popupMessages")
        defaults.synchronize()

        let application = UIApplication.shared
        let notificationTypes: UIUserNotificationType = [UIUserNotificationType.alert, UIUserNotificationType.badge, UIUserNotificationType.sound]
        let pushNotificationSettings = UIUserNotificationSettings(types: notificationTypes, categories: nil)

        application.registerUserNotificationSettings(pushNotificationSettings)
        application.registerForRemoteNotifications()
    } else {
        defaults.set("false", forKey: "popupMessages")
        defaults.synchronize()
    }
}

A simplified wrapper class I use (singleton used for simplicity):

class NotificationManager {
    static var shared = NotificationManager()

    private var application : UIApplication?

    func setup(application: UIApplication) {
        self.application = application
    }

    func register () {
        guard let application = application else {
            print("Attempt to register without calling setup")
            return
        }

        let notificationTypes: UIUserNotificationType = [UIUserNotificationType.alert, UIUserNotificationType.badge, UIUserNotificationType.sound]
        let pushNotificationSettings = UIUserNotificationSettings(types: notificationTypes, categories: nil)

        application.registerUserNotificationSettings(pushNotificationSettings)
        application.registerForRemoteNotifications()
    }
}

This requires that you call setup in your didFinishLaunchingMethod:

NotificationManager.shared.setup(application: application)

But then wherever you need to register for the notifications:

func messagesStateChanged(_ sender: UISwitch){
    if(sender.isOn){
        ...
        NotificationManager.shared.register()
    }
    ...
}

This allows you to easily test NotificationManager class by injecting an UIApplication mock object later on and the class also nicely encapsulates all notification related logic. With some modifications it can easily keep track if you already registered, keep track of the push token you received and even handle iOS 8/9 backward compatibility without duplicating boilerplate code.