In the previous tutorial, you learned how to schedule local notifications using the User Notifications framework. As I mentioned, the UILocalNotification class is deprecated as of iOS 10 and scheduling local notifications is a bit different if you use the User Notifications framework. And this also applies to notification actions. The result, however, is a transparent, flexible API that I am sure you are going to appreciate.

If you want to follow along, clone or download the project we created in the previous tutorial from GitHub.

Defining Actions

To make a notification created with the UILocalNotification class actionable, you need to manually add actions to the notification itself. The User Notifications framework works differently. Instead of manually adding actions to a notification, you register actions with a UNUserNotificationCenter instance.

Open the project in Xcode and navigate to the ViewController class. Update the viewDidLoad() method as shown below. We move the configuration of the shared UNUserNotificationCenter instance to a helper method, configureUserNotificationsCenter().

// MARK: - View Life Cycle

override func viewDidLoad() {
    super.viewDidLoad()

    configureUserNotificationsCenter()
}

Instead of setting the delegate of the shared user notifications center in the view controller's viewDidLoad() method, we set it in the configureUserNotificationsCenter() method.

private func configureUserNotificationsCenter() {
    // Configure User Notification Center
    UNUserNotificationCenter.current().delegate = self
}

But that is not why we created this helper method. Instead of manually adding actions to a notification, we need to define the actions for every notification, local and remote. This doesn't mean that you need to define every possible action in the configureUserNotificationsCenter() method. But I want you to understand that you need to inform the shared user notification center about the actions that can be assigned to a notification. Let me show you how it works.

To define an action, we create an instance of the UNNotificationAction class. The initializer accepts three parameters:

  • a unique identifier that is used to identify the action later
  • a title that is displayed to the user
  • a set of options

The first action we create enables the user to mark a tutorial for later reading. This is what the initialization of the action looks like.

let actionReadLater = UNNotificationAction(identifier: Notification.Action.readLater, title: "Read Later", options: [])

This action doesn't have any options. Note that I use a constant for the identifier. This is a pattern I really enjoy using and one that also made its way into Swift 3.

import UIKit
import UserNotifications

class ViewController: UIViewController {

    struct Notification {

        struct Action {
            static let readLater = "readLater"
        }

    }

    ...

}

When the user taps the Read Later action, the application can respond to the user's action, but the application doesn't enter the foreground. This is a benefit of notification actions.

With the user taps the second action, they explicitly want to see the details of the tutorial the application notifies them about. This is easy by passing in an option, foreground. This option instructs the User Notifications framework that the application should enter the foreground when this action is selected.

let actionShowDetails = UNNotificationAction(identifier: Notification.Action.showDetails, title: "Show Details", options: [.foreground])

With the third action, the user can unsubscribe from being notified about new tutorials.

let actionUnsubscribe = UNNotificationAction(identifier: Notification.Action.unsubscribe, title: "Unsubscribe", options: [.destructive, .authenticationRequired])

Because this action is destructive, we pass in two options:

  • destructive
  • authenticationRequired

By passing in the destructive option, the action is visually highlighted to indicate to the user the action is destructive. The second option informs the User Notifications framework that this action can only be performed when the device is unlocked. This adds a layer of security to the destructive action.

Defining a Category

Notification actions are always linked to a category. A category groups or categorizes actions and notifications. If a notification belongs to a category, the actions of that category are assigned to the notification. Let me show you how this works.

In the configureUserNotificationsCenter() method, we first define a category.

// Define Category
let tutorialCategory = UNNotificationCategory(identifier: Notification.Category.tutorial, actions: [actionReadLater, actionShowDetails, actionUnsubscribe], intentIdentifiers: [], options: [])

The initializer of the UNNotificationCategory class accepts four parameters:

  • a unique identifier that is used to identify the category
  • the actions associated with the category
  • a set of optional intent identifiers
  • a set of options

As before, we make use of structures to namespace the constants.

import UIKit
import UserNotifications

class ViewController: UIViewController {

    struct Notification {

        struct Category {
            static let tutorial = "tutorial"
        }

        struct Action {
            static let readLater = "readLater"
            static let showDetails = "showDetails"
            static let unsubscribe = "unsubscribe"
        }

    }

    ...

}

The actions we defined earlier are now associated with the tutorialcategory. Intent identifiers is not something we discuss in this tutorial.

Before we can assign a category to a notification, we need to register the category with the shared UNUserNotificationCenter instance. This is easy as you can see below.

// Register Category
UNUserNotificationCenter.current().setNotificationCategories([tutorialCategory])

This is what the completed configureUserNotificationsCenter() method looks like.

private func configureUserNotificationsCenter() {
    // Configure User Notification Center
    UNUserNotificationCenter.current().delegate = self

    // Define Actions
    let actionReadLater = UNNotificationAction(identifier: Notification.Action.readLater, title: "Read Later", options: [])
    let actionShowDetails = UNNotificationAction(identifier: Notification.Action.showDetails, title: "Show Details", options: [.foreground])
    let actionUnsubscribe = UNNotificationAction(identifier: Notification.Action.unsubscribe, title: "Unsubscribe", options: [.destructive, .authenticationRequired])

    // Define Category
    let tutorialCategory = UNNotificationCategory(identifier: Notification.Category.tutorial, actions: [actionReadLater, actionShowDetails, actionUnsubscribe], intentIdentifiers: [], options: [])

    // Register Category
    UNUserNotificationCenter.current().setNotificationCategories([tutorialCategory])
}

Assigning a Category to a Notification

The final piece of the puzzle is easy. With the actions and category defined, we can set the category identifier of the notification. We do this by setting the notification's categoryIdentifier property in the scheduleLocalNotification() method.

private func scheduleLocalNotification() {
    ...
    notificationContent.body = "In this tutorial, you learn how to schedule local notifications with the User Notifications framework."

    // Set Category Identifier
    notificationContent.categoryIdentifier = Notification.Category.tutorial

    // Add Trigger
    let notificationTrigger = UNTimeIntervalNotificationTrigger(timeInterval: 10.0, repeats: false)

    ...
}

The User Notifications framework is responsible for assigning the actions associated with the tutorial category to the notification.

Responding to Actions

The User Notifications framework is a significant step forward for creating, managing, and responding to local and remote notifications. To respond to notification actions, for example, we need to implement only one method, userNotificationCenter(_:didReceive:withCompletionHandler:). This method is invoked when the user taps a notification action, local or remote.

The implementation isn't interesting since we don't perform any action. But there are a few details I want to point out.

func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
    switch response.actionIdentifier {
    case Notification.Action.readLater:
        print("Save Tutorial For Later")
    case Notification.Action.unsubscribe:
        print("Unsubscribe Reader")
    default:
        print("Other Action")
    }

    completionHandler()
}

Your application is given a small amount of time to perform the task associated with the action the user tapped. When your application has finished, it needs to invoke the completion handler to notify the User Notifications framework it has finished processing the user's action.

The UNNotificationResponse object contains all the information your application needs to process the user's action. The class defines an actionIdentifier property and the response object also keeps a reference to the original UNNotification instance the action is associated with.

If the action accepts user input, the response parameter is of type UNTextInputNotificationResponse. This UNNotificationResponse subclass defines an additional property, userText, that holds the input of the user.

Build and Run

Build and run the application to test the actions we defined.

Local Notification With Actions

Local Notification With Actions On Lock Screen

What About Remote Notifications

The User Notifications framework treats remote notifications similar to local notifications. That is one of the major improvements the framework brings to the table. The user doesn't treat local and remote notifications differently and you don't need to either thanks to the User Notifications framework.

What's Next?

The User Notifications framework offers a rich API that vastly improves notification handling on iOS and watchOS. We only covered the basics in this short series.

You can download the source files of this tutorial from GitHub.