Local notifications in iOS (UNUserNotificationCenter)

Introduction

Notifications provide a mechanism for an application to schedule an alert to notify the user about an event. These notifications take the form of a notification panel containing a message accompanied by a sound and the vibration of the device.

Notifications are categorized as either local or remote. Notifications initiated by apps running on a device are referred to as local notifications. A remote notification, on the other hand, is initiated by a remote server and pushed to the device where it is displayed to the user.

Both local and remote notifications are managed using the classes of the UserNotifications framework in conjunction with the user notification center.

A notification consists of a content object, a request object and access to the notifications center. In addition, a notification may also include actions which appear in the form of buttons when a force touch press is performed on the notification message. These actions include the ability for the user to input text to be sent to the app that initiated the intent.

Before an app can issue notifications, it must first seek permission to do so from the user. This involves making a call to the user notification center.

The user notification center is responsible for handling, managing and coordinating all of the notifications on a device. In this case, a reference to the current notification center instance needs to be obtained and the requestAuthorization method called on that object.

Edit the ViewController.swift file to import the UserNotifications framework, request authorization and add a variable to store the subtitle of the message, which the user will be able to change from within the notification later in this tutorial:

import UIKit
import UserNotifications

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        UNUserNotificationCenter.current().requestAuthorization(options: [[.alert, .sound, .badge]],
            completionHandler: { (granted, error) in
            // Handle Error
        })
    }
}

Run the app on an iOS device and tap the Allow button when the permission request dialog appears.

Select the Main.storyboard file and drag and drop a Button object so that it is positioned in the center of the scene. Change the text on the button to read Send Notification, display the Assistant Editor panel and establish an action connection from the button to a method named buttonPressed.

Edit the ViewController.swift file and modify the buttonPressed action method to call a method named sendNotification:

@IBAction func buttonPressed(_ sender: Any) {
    sendNotification()
}

The first part of the notification to be created is the message content. The content of the message for a local notification is represented by an instance of the UNMutableNotificationContent class. This class can contain a variety of options including the message title, subtitle and body text. In addition, the message may contain media content such as images and videos. Within the ViewController.swift file, add the sendNotification method as follows:

func sendNotification() {
    let content = UNMutableNotificationContent()
    content.title = "Meeting Reminder"
    content.subtitle = "Meeting in 20 minutes"
    content.body = "Don't forget to bring coffee."
    content.badge = 1
}

Note also that the badge property of the content object is set to 1. This configures the notification count that is to appear on the app launch icon on the device after a notification has been triggered.

Once the content of the message has been created, a trigger needs to be defined that will cause the notification to be presented to the user. Local notifications can be triggered based on elapsed time interval, a specific date and time or a location change. For this example, the notification will be configured to trigger after 5 seconds have elapsed (without repeating).

let trigger = UNTimeIntervalNotificationTrigger(timeInterval:5, repeats: false)

The next step is to create a notification request object containing the content and trigger objects together with an identifier that can be used to access the notification later if it needs to be modified or deleted. The notification request takes the form of a UNNotificationRequest object, code for which will now need to be added to the sendNotification method:

let requestIdentifier = "demoNotification"
let request = UNNotificationRequest(identifier: requestIdentifier,
    content: content, trigger: trigger)

The request object now needs to be added to the notification center where it will be triggered when the specified time has elapsed:

UNUserNotificationCenter.current().add(request, withCompletionHandler: { (error) in
    // Handle error
})    

Compile and run the app on a device or simulator session. Once the app has loaded, tap the Send Notification button and press the Home button to place the app into the background. After 5 seconds have elapsed the notification will appear. Open the app once again and tap the button, but do not place the app into the background. This time the notification will not appear. This is the default behavior for user notifications. If the notifications issued by an app are to appear while that app is in the foreground, an additional step is necessary.

When an app that issues a notification is currently in the foreground the notification is not displayed. To change this default behavior, it will be necessary for the view controller to declare itself as conforming to the UNUserNotificationCenterDelegate protocol and implement the userNotification:willPresent method. The current class will also need to be declared as the notification delegate. Remaining within the ViewController.swift file, make these changes as follows:

class ViewController: UIViewController, UNUserNotificationCenterDelegate {
    override func viewDidLoad() {
        super.viewDidLoad()
        UNUserNotificationCenter.current().requestAuthorization(options:
            [[.alert, .sound, .badge]], completionHandler: { (granted, error) in
                // Handle Error
        })
        UNUserNotificationCenter.current().delegate = self
    }

    func userNotificationCenter(_ center: UNUserNotificationCenter, 
        willPresent notification: UNNotification, withCompletionHandler
        completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
            completionHandler([.alert, .sound])
            print("Received local notification \(notification)")
    }
}

The userNotification:willPresent method simply calls the provided completion handler indicating that the notification should be presented to the user using both the alert message and sound.

Adding Notification Actions

The default action when a notification is tapped by the user is to dismiss the notification and launch the corresponding app. The UserNotifications framework also allows action buttons to be added to notifications. These buttons are displayed when the user presses down with force on the notification message, or swipes the message to the left. For this example, two action buttons will be added to the notification, one of which will instruct the app to repeat the notification while the other will allow the user to input different text to appear in the message subtitle before also repeating the notification.

This will require the creation of two notification action objects as follows:

let repeatAction = UNNotificationAction(identifier:"repeat", title:"Repeat",options:[])
let changeAction = UNTextInputNotificationAction(identifier: "change", title: "Change Message", options: [])

Next, these action objects need to be placed in a notification category object and the category object added to the user notification center after being assigned an identifier:

let category = UNNotificationCategory(identifier: "actionCategory", actions: [repeatAction, changeAction],
    intentIdentifiers: [], options: [])
content.categoryIdentifier = "actionCategory"
UNUserNotificationCenter.current().setNotificationCategories([category])

Compile and run the app on a physical device, trigger the notification and perform a deep press on the message when it appears. The action buttons should appear beneath the message.

When an action is selected by the user, the userNotification:didReceive method of the designated notification delegate is called by the user notification center. Since the ViewController class has already been declared as implementing the UNUserNotificationCenterDelegate protocol and assigned as the delegate, all that needs to be added to the ViewController.swift file is the userNotification:didReceive method:

func userNotificationCenter(_ center: UNUserNotificationCenter, 
    didReceive response: UNNotificationResponse, 
    withCompletionHandler completionHandler: @escaping () -> Void) {
    switch response.actionIdentifier {
        case "repeat":
            self.sendNotification()
        case "change":
            let textResponse = response as! UNTextInputNotificationResponse
            messageSubtitle = textResponse.userText
            self.sendNotification()
        default:
            break
    }
    completionHandler()
}

The method is passed a UNNotificationResponse object from which we can extract the identifier of the action that triggered the call. A switch statement is then used to identify the action and take appropriate steps. If the repeat action is detected the notification is resent. In the case of the change action, the text entered by the user is obtained from the response object and assigned to the messageSubtitle variable before the notification is sent again.

Compile and run the app once again and verify that the actions perform as expected.

Managing Notifications

In addition to creating notifications, the UserNotifications framework also provides ways to manage notifications, both while they are still pending and after they have been delivered. One or more pending notifications may be removed by passing an array of request identifiers through to the removal method of the user notification center:

UNUserNotificationCenter.current()
    .removePendingNotificationRequests(withIdentifiers:
    [demoIdentifer, demoIdentifier2])

Similarly, the content of a pending intent may be updated by creating an updated request object with the same identifier containing the new content and adding it to the notifications center:

UNUserNotificationCenter.current().add(updatedRequest, withCompletionHandler: { (error) in
    // Handle error
})

Finally, previously delivered notifications can be deleted from the user’s notification history by a call to the user notification center passing through one or more request identifiers matching the notifications to be deleted:

UNUserNotificationCenter.current()
    .removeDeliveredNotifications(withIdentifiers: [demoIdentifier])

Scheduling notifications

let content = UNMutableNotificationContent() 
content.title = "Title"
content.body = "Body"
content.sound = UNNotificationSound.default

let calendar = Calendar(identifier: .gregorian) 
let components = calendar.dateComponents(
    [.year, .month, .day, .hour, .minute], from: dueDate)
let trigger = UNCalendarNotificationTrigger(dateMatching: components, repeats: false)

let request = UNNotificationRequest(identifier: "\(itemID)", 
    content: content, trigger: trigger)

let center = UNUserNotificationCenter.current() 
center.add(request)