How to setup local notifications using SWIFT and UILocalNotification

If you support local notifications in your app, the user will receive an alert on their device lock screen when your app is not running.  These alerts are displayed on the user's apple watch also if it is paired with the users iPhone.  The user must simply swipe to unlock the device and go to the app in one operation.
If you support local notifications in your app, the user will receive an alert on their device lock screen when your app is not running.  These alerts are displayed on the user's apple watch also if it is paired with the users iPhone.  The user must simply swipe to unlock the device and go to the app in one operation.
For any app with scheduling information or time based events this is a very powerful tool, which can enrich the app experience without any need for a push of the notifications from a remote server.
Let's setup a demo app to show how this is done.  We will cover the following tasks in the demo.
  1. Ask permission from the user for your app to provide notifications.
  2. Check if the user granted permission and display an alert informing them if they did not when they try to use a notification function in the app
  3. Schedule a local notification
  4. Remove a local notification, if it is no longer required
  5. Output a list of all notifications scheduled by the app at the moment.
Lets create a Single View Application with a group of buttons and a time picker to allow the user to schedule alerts based on the time picker for each of the buttons.
Screen-Shot-2016-03-20-at-21.24.06

We will call our project LocalNotifyDemo and save it to the desktop.  You can save it anywhere you like on your machine.

Screen-Shot-2016-03-20-at-21.26.16

Next we need our app to register for the UIUserNotificationSettings.  This is where we specific the UIUserNotificationType members which are of interested to us.  In this case we will register the UIUserNotificationTypes of .Alert, .Badge and .Sound.
So modify the
AppDelegate.swift file and in the ...didFinishLauchingWithOptions function add the following (in bold):

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {

application.registerUserNotificationSettings(UIUserNotificationSettings(forTypes: [.Alert , .Badge , .Sound], categories: nil))

return true
}

This will prompt the user to allow your app to send notification messages or not.  As the user can select not to do this you need to handle the case where they decline to accept notifications.  See step 2 below for details.
If we compile and run our application now it will prompt us to allow notifications from the app the first time it runs.  Press "Command R" now to run the app in the simulator or on your connected device.


Screen-Shot-2016-03-20-at-22.00.02

If we press OK then notifications are enabled.  Otherwise they are not enabled and the app will not ask again next time it is run.  If the user clicked "Dont't Allow" then they will need to go to the Settings menu on the device and enable notifications manually if they change their mind in the future as follows.
Screen-Shot-2016-03-20-at-22.03.37Screen-Shot-2016-03-20-at-22.03.43Screen-Shot-2016-03-20-at-22.03.55Screen-Shot-2016-03-20-at-22.04.03


Add a new Swift class  file to your project by entering "Command N" or File Menu -> New -> File -> iOS -> Source -> Cocoa Touch Class.

Screen-Shot-2016-03-30-at-14.06.31

Name the new file
LocalNotificationHelper as a subclass of NSObject with the language Swift selected and press Create to save it to your project.  This class will eventually perform four functions for us.

Screen-Shot-2016-03-30-at-14.08.36
  1. It will create a new notification based on the passed in parameters.
  2. It will delete an existing notification based on the passed in parameters.
  3. It will print for debugging purposes to the console a list of all pending notifications scheduled by the app on the device.

Replace the contents of the file with the following:

//
// LocalNotificationHelper.swift
// LocalNotifyDemo
//
// Created by Kevin Horgan on 30/03/16.
// Copyright © 2016 Balanced Code GmbH. All rights reserved.
//

import UIKit

class LocalNotificationHelper: NSObject {

func checkNotificationEnabled() -> Bool {
// Check if the user has enabled notifications for this app and return True / False
guard let settings = UIApplication.sharedApplication().currentUserNotificationSettings() else { return false}
if settings.types == .None {
return false
} else {
return true
}
}

func checkNotificationExists(taskTypeId: String) -> Bool {
// Loop through the pending notifications
for notification in UIApplication.sharedApplication().scheduledLocalNotifications! as [UILocalNotification] {

// Find the notification that corresponds to this task entry instance (matched by taskTypeId)
if (notification.userInfo!["taskObjectId"] as! String == String(taskTypeId)) {
return true
}
}
return false

}

func scheduleLocal(taskTypeId: String, alertDate: NSDate) {

let notification = UILocalNotification()
notification.fireDate = alertDate
notification.alertBody = "Task \(taskTypeId)"
notification.alertAction = "Due : \(alertDate)"
notification.soundName = UILocalNotificationDefaultSoundName
notification.userInfo = ["taskObjectId": taskTypeId]
UIApplication.sharedApplication().scheduleLocalNotification(notification)

print("Notification set for taskTypeID: \(taskTypeId) at \(alertDate)")
}

func removeNotification(taskTypeId: String) {

// loop through the pending notifications
for notification in UIApplication.sharedApplication().scheduledLocalNotifications! as [UILocalNotification] {

// Cancel the notification that corresponds to this task entry instance (matched by taskTypeId)
if (notification.userInfo!["taskObjectId"] as! String == String(taskTypeId)) {
UIApplication.sharedApplication().cancelLocalNotification(notification)

print("Notification deleted for taskTypeID: \(taskTypeId)")

break
}
}
}

func listNotifications() -> [UILocalNotification] {
var localNotifyEmbarrassedUILocalNotification]?
for notification in UIApplication.sharedApplication().scheduledLocalNotifications! as [UILocalNotification] {
localNotify?.append(notification)
}
return localNotify!
}

func printNotifications() {

print("List of notifications currently set:- ")

for notification in UIApplication.sharedApplication().scheduledLocalNotifications! as [UILocalNotification] {
print ("\(notification)")
}
}
}

Next we need to switch to the Storyboard and build the interface.  Open the
Storyboard file and add 3 switches for the different alert times we wish to set.  Then add three labels and call these these "Task A) 60 seconds", "Task B) 120 seconds" and "Task C) 180 seconds".  Below this add a UITextView to display the notification which is set and finally add a UIButton with the title "PRINT ALL NOTIFICATIONS" which will just dump all of the notifications to the console.
You interface should look something like this when you are complete.
Screen-Shot-2016-03-30-at-14.44.47

Now we will wire up the interface components to the
ViewController.swift.  In the Storyboard, open the Show Assistant Editor and Ctrl-Drag from each switch, the Text View and the Button to the ViewController.swift file to setup the IBOutlets.
Screen-Shot-2016-03-30-at-14.48.35

I have created the following IBOutlets above:

@IBOutlet weak var taskASwitch: UISwitch!
@IBOutlet weak var taskBSwitch: UISwitch!
@IBOutlet weak var taskCSwitch: UISwitch!
@IBOutlet weak var textView: UITextView!
@IBOutlet weak var printButton: UIButton!

Next lets set each of the switches to ON or OFF depending on if a Notification is currently set and make the empty the textView.  Replace the code in the
ViewController.swift  viewDidLoad function with the following:

override func viewDidLoad() {
super.viewDidLoad()

// Set the three switches to ON or OFF depending on if a notification is currently scheduled.
taskASwitch.setOn(LocalNotificationHelper().checkNotificationExists("A"), animated: true)
taskBSwitch.setOn(LocalNotificationHelper().checkNotificationExists("B"), animated: true)
taskCSwitch.setOn(LocalNotificationHelper().checkNotificationExists("C"), animated: true)

textView.text = nil
}

Now we will add an IBActions for each of the three Switches and the print Button we have in our
Storyboard.  Control drag from each Switch and the Button to the ViewController.swift in the Assistant Editor window and add the following:-

@IBAction func taskASwitchSet(sender: AnyObject) {
}

@IBAction func taskBSwitchSet(sender: AnyObject) {
}

@IBAction func taskCSwitchSet(sender: AnyObject) {
}

@IBAction func printButtonClicked(sender: AnyObject) {

}

If the switch is set to ON state from an OFF state, we will first check if the you will allow notifications.  If yes we will set a local notification.  If no, then we will display a popup alert message and return the switch to the OFF state.  If the notification is successfully set then we will display a message in the Text View box below the switches.

If the switch is set to OFF state from an ON state, we will search for a pending notification and if found we will delete it, so preventing the notification from firing as scheduled.

In the 
ViewController.swift class implement the three @IBAction functions for the Switches created above as follows:-

@IBAction func taskASwitchSet(sender: UISwitch) {
let taskTypeId:String = "A" // Task id string to display in the notification
let secondsDelay:Int = 60 // Seconds delay from current date for notification

// Add the secondsDelay to the current system date and time for the notification fireDate
let calendar = NSCalendar.currentCalendar()
let date = calendar.dateByAddingUnit(.Second, value: secondsDelay, toDate: NSDate(), options: [])

notificationSetter(sender, taskTypeId: taskTypeId, fireDate: date!)
}

@IBAction func taskBSwitchSet(sender: UISwitch) {
let taskTypeId:String = "B"
let secondsDelay:Int = 120 // Seconds delay from current date for notification

// Add the secondsDelay to the current system date and time for the notification fireDate
let calendar = NSCalendar.currentCalendar()
let date = calendar.dateByAddingUnit(.Second, value: secondsDelay, toDate: NSDate(), options: [])

notificationSetter(sender, taskTypeId: taskTypeId, fireDate: date!)

}

@IBAction func taskCSwitchSet(sender: UISwitch) {
let taskTypeId:String = "C"
let secondsDelay:Int = 180 // Seconds delay from current date for notification

// Add the secondsDelay to the current system date and time for the notification fireDate
let calendar = NSCalendar.currentCalendar()
let date = calendar.dateByAddingUnit(.Second, value: secondsDelay, toDate: NSDate(), options: [])

notificationSetter(sender, taskTypeId: taskTypeId, fireDate: date!)

}

In each of the above @IBAction functions, we first set the taskTypeID value as the string "A, "B" or "C" depending on the switch selected.  Then we define a constant for the number of seconds to add to the current system time which will be the date and time for when the Notification should fire.  Finally we call a private function which we will implement below to configure the Notification.

Now add the private function
notificationSetter  just below the four @IBAction function definitions.

private func notificationSetter(sender: UISwitch, taskTypeId: String, fireDate: NSDate) {

// If the switch is set to ON
if sender.on == true {

// Check if local notifications are enabled for this app
if LocalNotificationHelper().checkNotificationEnabled() == true {

// If local notifications are enabled, schedule the notification, write to the Text View and set the Switch to ON
sender.setOn(true, animated: true)
LocalNotificationHelper().scheduleLocal(taskTypeId, alertDate: fireDate)
textView.text = "Notification taskTypeId: \(taskTypeId) at \(fireDate)"

} else {

// If local notifications are disabled, display the alert popup and reset the Switch to OFF
sender.setOn(false, animated: true)

displayNotificationsDisabled()
}
} else {
sender.setOn(false, animated: true)

if LocalNotificationHelper().checkNotificationExists(taskTypeId) == true {
textView.text = "Removing Notification taskTypeId: \(taskTypeId)"
LocalNotificationHelper().removeNotification(taskTypeId)
}
}
}

The above function will check if we are setting the sender (Switch) to ON or OFF.

If the Switch is set to ON then check if the user has granted the app to send Notifications with the function call  
LocalNotificationHelper().checkNotificationEnabled().  If this returns TRUE then set the sender (Switch) to the ON state.  Schedule a new Local Notification by calling LocalNotificationHelper().scheduleLocal() and update the Text View text with the Notification details.

If the user has not granted the app to send Notifications, then set the sender (Switch) back to the OFF state and call the private function
displayNotificationsDisabled() which will will implement next to warn the user Notifications are disabled for the app.

Finally, if the user has set the sender (Switch) to the OFF state, then check if an existing Local Notification matching this switch with
LocalNotificationHelper().checkNotificationExists() is already scheduled and if yes, remove it with LocalNotificationHelper().removeNotification().

Next we can implement the private function
displayNotificationsDisabled() to display the popup alert if the user has not granted the app to display Notifications.

private func displayNotificationsDisabled() {
let alertController = UIAlertController(
title: "Notifications disabled for LocalNotifyDemo App",
message: "Please enable Notifications in Settings -> Notifications -> LocalNotifyDemo",
preferredStyle: UIAlertControllerStyle.Alert)

alertController.addAction(UIAlertAction(
title: "DISMISS",
style: UIAlertActionStyle.Default,
handler: nil))

self.presentViewController(alertController, animated: true, completion: nil)
}

If the user has not granted the app the authority to send Notifications, then when the user tries to use a Switch to set a Notification, they will see the following Alert Popup warning them it is not possible until they change the Notification settings for the app in the devices Settings menu.

Screen-Shot-2016-03-30-at-16.09.44

Finally we need to add the implementation for the @IBAction function printButtonClicked() to display all scheduled Notifications to the Xcode console for debug purposes.

@IBAction func printButtonClicked(sender: AnyObject) {

// Print all notifications to the console window for debugging
LocalNotificationHelper().printNotifications()
}

Now compile and run.  Set one of the Switches in the Simulator or the device and then press the Home Button (Command + Shift + H in the simulator) to put the app into the background.  Then sit and wait for the number of seconds you have chosen to see your Notification.  Swipe to the right and you should enter your application directly.

Screen-Shot-2016-03-30-at-16.32.13


Here is the completed
ViewController class once more incase you had any problems with the above code fragments.


//
// ViewController.swift
// LocalNotifyDemo
//
// Created by Kevin Horgan on 30/03/16.
// Copyright © 2016 Balanced Code GmbH. All rights reserved.
//

import UIKit

class ViewController: UIViewController {

@IBOutlet weak var taskASwitch: UISwitch!
@IBOutlet weak var taskBSwitch: UISwitch!
@IBOutlet weak var taskCSwitch: UISwitch!
@IBOutlet weak var textView: UITextView!
@IBOutlet weak var printButton: UIButton!


override func viewDidLoad() {
super.viewDidLoad()
taskASwitch.setOn(LocalNotificationHelper().checkNotificationExists("A"), animated: true)
taskBSwitch.setOn(LocalNotificationHelper().checkNotificationExists("B"), animated: true)
taskCSwitch.setOn(LocalNotificationHelper().checkNotificationExists("C"), animated: true)
textView.text = nil
}

override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}

@IBAction func taskASwitchSet(sender: UISwitch) {
let taskTypeId:String = "A" // Task id string to display in the notification
let secondsDelay:Int = 60 // Seconds delay from current date for notification

// Add the secondsDelay to the current system date and time for the notification fireDate
let calendar = NSCalendar.currentCalendar()
let date = calendar.dateByAddingUnit(.Second, value: secondsDelay, toDate: NSDate(), options: [])

notificationSetter(sender, taskTypeId: taskTypeId, fireDate: date!)
}

@IBAction func taskBSwitchSet(sender: UISwitch) {
let taskTypeId:String = "B"
let secondsDelay:Int = 120 // Seconds delay from current date for notification

// Add the secondsDelay to the current system date and time for the notification fireDate
let calendar = NSCalendar.currentCalendar()
let date = calendar.dateByAddingUnit(.Second, value: secondsDelay, toDate: NSDate(), options: [])

notificationSetter(sender, taskTypeId: taskTypeId, fireDate: date!)

}

@IBAction func taskCSwitchSet(sender: UISwitch) {
let taskTypeId:String = "C"
let secondsDelay:Int = 180 // Seconds delay from current date for notification

// Add the secondsDelay to the current system date and time for the notification fireDate
let calendar = NSCalendar.currentCalendar()
let date = calendar.dateByAddingUnit(.Second, value: secondsDelay, toDate: NSDate(), options: [])

notificationSetter(sender, taskTypeId: taskTypeId, fireDate: date!)

}

@IBAction func printButtonClicked(sender: AnyObject) {

// Print all notifications to the console window for debugging
LocalNotificationHelper().printNotifications()
}

private func notificationSetter(sender: UISwitch, taskTypeId: String, fireDate: NSDate) {

// If the switch is set to ON
if sender.on == true {

// Check if local notifications are enabled for this app
if LocalNotificationHelper().checkNotificationEnabled() == true {

// If local notifications are enabled, schedule the notification, write to the Text View and set the Switch to ON
sender.setOn(true, animated: true)
LocalNotificationHelper().scheduleLocal(taskTypeId, alertDate: fireDate)
textView.text = "Notification taskTypeId: \(taskTypeId) at \(fireDate)"

} else {

// If local notifications are disabled, display the alert popup and reset the Switch to OFF
sender.setOn(false, animated: true)

displayNotificationsDisabled()
}
} else {
sender.setOn(false, animated: true)

if LocalNotificationHelper().checkNotificationExists(taskTypeId) == true {
textView.text = "Removing Notification taskTypeId: \(taskTypeId)"
LocalNotificationHelper().removeNotification(taskTypeId)
}
}
}

private func displayNotificationsDisabled() {
let alertController = UIAlertController(
title: "Notifications disabled for LocalNotifyDemo App",
message: "Please enable Notifications in Settings -> Notifications -> LocalNotifyDemo",
preferredStyle: UIAlertControllerStyle.Alert)

alertController.addAction(UIAlertAction(
title: "DISMISS",
style: UIAlertActionStyle.Default,
handler: nil))

self.presentViewController(alertController, animated: true, completion: nil)
}
}





blog comments powered by Disqus