Local Notifications in Swift 3.0

“We all need assistants in today’s life who can remind us our daily tasks and It will be a cherry on the cake if our smart phones play that role!!”

iOS  is packed with this feature with the name of ‘Local Notifications’. They work like reminders and pops on the navigation bar. Simply, with iOS 4 release, notifications which can be scheduled & fired by the system are referred as Local Notifications. Lets dig dipper.

The Requirement:

Now, if I want to enable this feature to my application, what do I do? What I know is –

  1.  The content to display.
  2.  The date to get that reminder on.
  3.  The intervals I want to repeat on
  4.  The actions I want for my user etc..

But,  who to tell all of above and notify according to my requirement. Thus, frameworks come to rescue. You just need to tell your requirements to them & they take the entire load from scheduling to firing, from content to actions etc. UIKit framework (deprecated in iOS 10) & UserNotifications framework (from iOS 10 &Later) provides its classes & methods to handle all of it.

We will deal with both frameworks i.e UIKit (For iOS 9 & Below) & UserNotifications (For iOS 10 & Later)

Note : It is recommendation – UILocalNotification is deprecated in iOS 10. Use UNNotificationRequest instead .

Step 1: The Request

First things first. We need to request the user to allow our application to send him notifications. This will be done at launch of application. Add the following code in

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool method of AppDelegate.swift file

For iOS 9 & below –

        let notificationTypes: UIUserNotificationType

        notificationTypes = [.alert , .sound]

        

        let newNotificationSettings = UIUserNotificationSettings(types: notificationTypes, categories: nil)

        UIApplication.shared.registerUserNotificationSettings(newNotificationSettings)

 

For iOS 10 &Later-

import UserNotifications framework first – import UserNotifications.

UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound]) {(accepted, error) in

                if !accepted {

                    print("Notification access denied.")

                }

Step 2: The Content:

Now we need to put the details of notification like when to fire it, what title & description of notification will be, What sound it will buzz etc. Then, encapsulating the data into parameters with that content, we will schedule the notification. The notification will be fired after 2 minutes & will be repeated every hour.

For iOS 9 & below –

           let date = Date(timeIntervalSinceNow:2*60)

            let notification = UILocalNotification()

            notification.fireDate = date

            notification.alertTitle = "Reminder"

            notification.alertBody = "Car Wash : Take Car to garage"

            notification.repeatInterval = NSCalendar.Unit.hour

            UIApplication.shared.scheduleLocalNotification(notification)

 

For iOS 10 & Later-

There is a new candy in the bucket list of UserNotifications framework which is attachment. Yes, now you can attach any image, audio, video etc. Also, to set the fire date in UserNotifications framework, we need to make variable of UNCalendarNotificationTrigger & set repeats property there itself.

let date = Date(timeIntervalSinceNow:2*60)

let triggerDaily = Calendar.current.dateComponents([.hour,.minute,.second], from: date)

let trigger:UNCalendarNotificationTrigger = UNCalendarNotificationTrigger(dateMatching: triggerDaily, repeats: true)

let content = UNMutableNotificationContent()

            content.title = "Reminder"

            content.body = "Car Wash:Take car to garage"
              //add an image in the project & then
             //specify the path for the image & then include it in attachments

            if let path = Bundle.main.path(forResource: "ping_me", ofType: "png") {

                let url = URL(fileURLWithPath: path)   

                do {

                    let attachment = try UNNotificationAttachment(identifier: "ping_me", url: url, options: nil)

                    content.attachments = [attachment]

                } catch {

                    print("The attachment was not loaded.")

                }

let request = UNNotificationRequest(identifier: content.title, content: content, trigger: trigger)

            UNUserNotificationCenter.current().add(request, withCompletionHandler: {(error) in
                     print("error")
            })

Step 3: The Actions:

At this point, we are in a position to get the notification but lets give more power to the user and enable some actions with notification. So, lets add actions to our notifications. As, the implementation has drastic differences between iOS 9 & iOS 10, So will deal differently.

Notification Actions in iOS 9 & below:

An action is an object of the UIMutableUserNotificationAction class. This class was introduced in iOS 8, and provides various useful properties so an action can be properly configured like identifier, title, destructive, authenticationRequired, activationMode. For every action, we will set the values to all the aforementioned properties. The first one:

        let actionRemindMeLater = UIMutableUserNotificationAction()

        actionRemindMeLater.identifier = "Remind Me Later"

        actionRemindMeLater.title = "Remind Me Later"

        actionRemindMeLater.activationMode = UIUserNotificationActivationMode.background

        actionRemindMeLater.isDestructive = false

        actionRemindMeLater.isAuthenticationRequired = false

The second one:

        let actionSetNewReminder = UIMutableUserNotificationAction()

        actionSetNewReminder.identifier = "Set New Reminder"

        actionSetNewReminder.title = "Set New Reminder"

        actionSetNewReminder.activationMode = UIUserNotificationActivationMode.foreground

        actionSetNewReminder.isDestructive = false

        actionSetNewReminder.isAuthenticationRequired = false

As the identifier and title of action suggests, first action “Remind Me Later” will normally just remove notification from Notification Bar. The second action “Set New Reminder” will set new reminder after 10 minutes to give an extra reminder & will bring our application foreground.

When the actions of a notification have been defined, they can then be grouped together in categories.

A category is an object of the UIMutableUserNotificationCategory class, which was also introduced in iOS 8. This class has just one property and one method. The property is a string identifier that uniquely identifies a category (similarly to actions), and the method is used to group the actions together. Next, let’s create a new category for which we will set an identifier. 

        let myCategory = UIMutableUserNotificationCategory()

        myCategory.identifier = "my category"

        myCategory.setActions([actionRemindMeLater,actionSetNewReminder], for: .default)

        let categoriesForSettings = NSSet(objects: myCategory)

Change the UIUserNotificationSettings(types: ,categories:)  and add the category defined above inUIUserNotificationSettings as a parameter.

let newNotificationSettings = UIUserNotificationSettings(types: notificationTypes, categories: categoriesForSettings as? Set<UIUserNotificationCategory>)

        UIApplication.shared.registerUserNotificationSettings(newNotificationSettings)

Last thing left ,  that is to assign category to the object of UILocalNotification along with fireDate, alertTitle, alertBody & repeatInterval set above in code.

            notification.category = "my category"

Good to go!! Now you have told the framework to add actions to your notification when fired. But, the question arises even if the actions are added how will your application know that a certain action has been performed? Also, because we tell the framework to schedule everything therefore there is nothing in our hand.

This implies that the framework must provide some callback methods to us where we can guide the application to proceed with our code.

So, we will implement delegate method in AppDelegate.swift file which will get the callback on action performed on UIMutableUserNotificationAction actions 

func application(_ application: UIApplication, handleActionWithIdentifier identifier: String?, for notification: UILocalNotification, completionHandler: @escaping () -> Void):

func application(_ application: UIApplication, handleActionWithIdentifier identifier: String?, for notification: UILocalNotification, completionHandler: @escaping () -> Void) {

        if identifier == "Remind Me Later" {

            NotificationCenter.default.post(name: NSNotification.Name.init(rawValue: "remindLater"), object: nil)

        }

        else if identifier == "Set New Reminder" {            

            NotificationCenter.default.post(name: NSNotification.Name.init(rawValue: "setNewReminder"), object: nil)

        }   

        completionHandler()

    }

Based on the identifiers of actions, both cases we post a notification (NSNotification), specifying a unique identifier. Depending on the identifier value, we will post a notification (NSNotification) for each one, and then in the ViewController class we’ll observe for those notifications. Notice that we have called completionHandler() at the end of this method as this is important for system to tell that we have handled the received actions.

Next, we will add observer in viewController.swift for notifications we set before and then handle actions in our custom methods.

NotificationCenter.default.addObserver(self, selector: #selector(handleRemindLater(notification:)), name: NSNotification.Name(rawValue: "remindLater"), object: nil)

NotificationCenter.default.addObserver(self, selector: #selector(handleSetNewReminder(notification:)), name: NSNotification.Name(rawValue: "setNewReminder"), object: nil)

The following custom methods will be added to our viewController file:

@objc static func handleRemindLater(notification:NSNotification) {

        //code to perform on remind me later

       //Since we are not performing any task on its action, so default action will be performed dismissing notification.In case,you want to perform some task,add code here.

    }

@objc static func handleSetNewReminder(notification:NSNotification) { 

        //code to perform on set reminder

        let notification = UILocalNotification()

            let date = Date(timeIntervalSinceNow:10*60)

            notification.fireDate = date

            notification.alertTitle = "Reminder"

            notification.alertBody ="Car Wash : Take Car to garage-Repeat"

            notification.category = "my category"

            UIApplication.shared.scheduleLocalNotification(notification)

}

So, this finish the action handling with Notification in iOS 9 & below. Next, one case is still left which is that if application is in foreground & notification is fired, then what? Therefore, while application is in foreground, we get a delegate method where we get a call in 

func application(_ application: UIApplication, didReceive notification: UILocalNotification. Add this method in AppDelegate.swift file.

func application(_ application: UIApplication, didReceive notification: UILocalNotification) {

        let ok:UIAlertAction = UIAlertAction(title: "OK", style: .default, handler:{ (alertAction:UIAlertAction!) -> Void in})

        let alert:UIAlertController = UIAlertController(title: notification.alertTitle, message: notification.alertBody, preferredStyle: .alert)

        alert.addAction(ok)

        let navigationController = UIApplication.shared.keyWindow?.rootViewController as! UINavigationController

        navigationController.present(alert, animated: true, completion: nil)

    }

Here we have added an alert which will pop up on screen showing notification message while you are still running application.

This completes the complete Notification Handling in iOS 9 & below.

Notification Actions in iOS10 & later:

We implemented all delegate methods for iOS 9 in AppDelegate file because notification framework for iOS 9 & below comes in UIKit framework(available by default). But this is not the case for iOS 10 & Later as the framework handling notifications needs manual import. So, to implement delegate methods for iOS 10, we need to first confirm UNUserNotificationCenterDelegate to the AppDelegate.swift file.

So, lets make extension of AppDelegate and confirm the protocol  UNUserNotificationCenterDelegate.

extension AppDelegate : UNUserNotificationCenterDelegate { 
}

Assign its delegate to self in AppDelegate:

            UNUserNotificationCenter.current().delegate = self

Now, the upcoming parts are pretty much similar to the implementation done for iOS 9 in fact simpler than UILocalNotifications. First let us switch & add actions to our notification. We will add the following code in AppDelegate.swift to add actions to our notification:

let actionRemindLater = UNNotificationAction(identifier: "Remind Me Later", title: "Remind Me Later", options: [])

let actionNewReminder = UNNotificationAction(identifier: "Set New Reminder", title: "Set New Reminder", options: [.foreground])

Next we will add above actions to a category and then register category to  UNUserNotificationCenter

let category = UNNotificationCategory(identifier: "myCategory", actions: [actionRemindLater,actionNewReminder], intentIdentifiers: [], options: [.customDismissAction])

            UNUserNotificationCenter.current().setNotificationCategories([category])

Now tell the content of UNMutableNotificationContent about the category you have defined.

content.categoryIdentifier = "myCategory"

Now, we are just left with handling actions on getting call in our application. So the protocol  (UNUserNotificationCenterDelegate) we confirmed, has 2 methods to handle notifications. The first method –

func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) 

gives a callback in the application when any of the defined action is performed & application is in background. Add this method in the extension of AppDelegate :

func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {

        if response.actionIdentifier == "Remind Me Later" {
         
        } else if response.actionIdentifier == "Set New Reminder" {

         }
}

Based on the action identifier, same tasks will be executed as in iOS 9.

 func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {

        if response.actionIdentifier == "Remind Me Later" {
         //Add code here to perform task on tap of this action.Fornow,it performs default action

       } else if response.actionIdentifier == "Set New Reminder" {

          let date = Date(timeIntervalSinceNow:10*60)

          let triggerDaily = Calendar.current.dateComponents([.hour,.minute,.second], from: date)

          let trigger:UNCalendarNotificationTrigger = UNCalendarNotificationTrigger(dateMatching: triggerDaily, repeats: false)

          let content = UNMutableNotificationContent()

            content.title = "Reminder"

            content.body = "Car Wash:Take car to garage-Repeat"

              //add an image in the project & then

             //specify the path for the image & then include it in attachments

            if let path = Bundle.main.path(forResource: "ping_me", ofType: "png") {

                let url = URL(fileURLWithPath: path)   

                do {

                    let attachment = try UNNotificationAttachment(identifier: "ping_me", url: url, options: nil)

                    content.attachments = [attachment]

                } catch {

                    print("The attachment was not loaded.")

                }

        }

}

Next, just 1 step ahead which is to add alert when application is in foreground & notification is fired. Implement this method in AppDelegate extension:

func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) and add alert in it.

func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {

        

        let ok:UIAlertAction = UIAlertAction(title: "OK", style: .default, handler:{ (alertAction:UIAlertAction!) -> Void in})

        let alert:UIAlertController = UIAlertController(title: notification.request.content.title, message: notification.request.content.body, preferredStyle: .alert)

        alert.addAction(ok)

        let navigationController = UIApplication.shared.keyWindow?.rootViewController as! UINavigationController

        navigationController.present(alert, animated: true, completion: nil)

        

        completionHandler([.alert, .sound])

    }

The Summary:

Well, this completes the entire implementation on Local Notifications. Keep cool Keep coding!!!

Leave a Reply