In a previous tutorial, we looked at how to send a local notification. Using the basic code provided by Apple you can trigger notifications to fire almost immediately as well as at a specified date and time in the future. In this tutorial we’ll expand on this by looking at the associated delegate protocol; UNUserNotificationCenterDelegate; which allows us to detect which notification was swiped, and also detect when a notification is triggered when the app is in the foreground. To begin, download the project from here which we created in the last tutorial.
Implementing the UNUserNotificationCenterDelegate Protocol
When a notification arrives on the lock screen; or as a banner across the top if the phone is unlocked; swiping or taping on it will open the app. Often the notifications refer to something specific within an app. Think of a reminder appearing on the lock screen. When you swipe it, you probably want the app to open the correct reminder so you can mark it off as complete. To accomplish this you need to implement the delegate protocol defined for the UserNotifications framework. The delegate protocol is called UNUserNotificationCenterDelegate and has just 2 methods within. The one we need for detecting which notification was swiped is: userNotificationCenter(_:didReceive:withCompletionHandler:).
Open ViewController.swift from the project that you downloaded earlier. Declare that you are adopting the UNUserNotificationCenterDelegate protocol as follows:
class ViewController: UIViewController, UNUserNotificationCenterDelegate {
Note that here we just add the UNUserNotificationCenterDelegate protocol just after the UIViewController.
Modify viewDidLoad by adding the following:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
UNUserNotificationCenter.current().delegate = self
}
Here, we are specifying that this class is the delegate and will be implementing one or both of the available methods. For now, we’ll just add the userNotificationCenter(_:didReceive:withCompletionHandler:) method as follows:
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
print("Test: \(response.notification.request.identifier)")
}
You add this by just typing “userNoti” and autocomplete will list the methods available to choose from. Hit enter when you select the correct one. Both look very similar, so look at the brief note just below the autocomplete options to help you decide which one is correct.
On line 2 we are printing the response to make sure it is working as needed. To detect which notification was used to open the app, we inspect the identifier which is part of the request which is part of the notification which is part of the response. Because we set up the notification in the previous tutorial to have an identifier called “FiveSeconds”, this is what appears in the console. In a live app you’ll probably have some identifier from an entity in CoreData that can be used to fetch the correct object as needed. For the purpose of this test app, we just manually hard code “FiveSeconds” to demonstrate the data that is passed between.
If you run the app now, you’ll notice a warning in the console saying:
"Warning: UNUserNotificationCenter delegate received call to -userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler: but the completion handler was never called."
This is fixed by calling completionHandler() at the end of the function as follows:
response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
print("Test: \(response.notification.request.identifier)")
completionHandler()
}
Calling completionHandler() lets the system know that you have finished what needed to be done in your implementation or in other words, it simply says “I’m done here”.
Handling Notifications in the Foreground
The other method available in the UNUserNotificationCenterDelegate protocol is userNotificationCenter(_:willPresent:withCompletionHandler:). If the app is running when a notification is triggered then if implemented, this method will be called to let you know that a notification has just fired. At this point, you can choose how to handle this. If creating a reminders app, you might put an alert on screen with a UIAlertController to show the user that a reminder is due and needs to be completed. If this method is not implemented, then any notifications that trigger while the app is in the foreground are just ignored. Alternatively, you can specify [.alert, .sound] in the completion handler and this will cause the regular notification to appear at the top of the screen.
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
print("Test Foreground: \(notification.request.identifier)")
completionHandler([.alert, .sound])
}
If specifying .alert then tapping on this would cause the other delegate method to then be called. Just be careful of this incase you fetch data twice. If you fetch data in the willPresent version and then use the completion handler and the user taps on the notification, then the other delegate method might also fetch data.
Note that we are being passed a UNNotification instead of a UNNotificationResponse. The reason for this is that the other delegate method contains the actionIdentifier property which is part of the UNNotificationResponse. The UNNotificationResponse also contains a UNNotification object. Notifications the lock screen and on the homepages have extra functions which you can customise such as being able to reply to an email, mark off a task as complete, delete a message, and whatever else you can think of.
If you run the app now, you will get a notification 5 seconds after tapping the button. If you are in the app, it will appear at the top. If you are at the lock screen, it will appear there, and if on the home screen, it will slide down from the top. Tapping any of them will print the identifier to the console.
Working with User Interactions with UNNotificationAction and UNNotificationCategory
For the final part of this tutorial we’ll look at how to make notifications actionable. You may be familiar with using this on apps such as mail or messages that allow you to swipe down on the notification and then perform a few simple tasks such as deleting a message, archiving it, or opening the app to send a reply.
With the delegate methods already set up, we just need to add a few more lines of code to make this work. The first additions we need to make are in the userNotificationCenter(_:didReceive:withCompletionHandler:) method.
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
print("Test: \(response.notification.request.identifier)")
switch response.actionIdentifier {
case "Complete":
print("Complete")
completionHandler()
case "Edit":
print("Edit")
completionHandler()
case "Delete":
print("Delete")
completionHandler()
default:
completionHandler()
}
completionHandler()
}
We are adding lines 4 – 15 here which is just a standard switch statement that looks at the response.actionIdentifier. If there is an action identifier, it will select the appropriate course of action. In this example, we will simply log the action taken to the console. Using this on a real app you would likely get the identifier from response.notification.request.identifier and then depending on the action you might “complete” the action, i.e., update CoreData to indicate that a task is complete, or you might bring up an edit window, or you might just delete the task.
Next, we need to update the “sendNotificationIn5Seconds” method:
@IBAction func sendNotificationIn5Seconds() {
let centre = UNUserNotificationCenter.current()
centre.getNotificationSettings { (settings) in
if settings.authorizationStatus != UNAuthorizationStatus.authorized {
print("Not Authorised")
} else {
print("Authorised")
let content = UNMutableNotificationContent()
content.title = NSString.localizedUserNotificationString(forKey: "This is the title", arguments: nil)
content.body = NSString.localizedUserNotificationString(forKey: "The message body goes here.", arguments: nil)
content.categoryIdentifier = "Category"
// Deliver the notification in five seconds.
content.sound = UNNotificationSound.default()
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 5, repeats: false)
// Schedule the notification.
let request = UNNotificationRequest(identifier: "FiveSecond", content: content, trigger: trigger)
let center = UNUserNotificationCenter.current()
let completeAction = UNNotificationAction.init(identifier: "Complete", title: "Complete", options: UNNotificationActionOptions())
let editAction = UNNotificationAction.init(identifier: "Edit", title: "Edit", options: UNNotificationActionOptions.foreground)
let deleteAction = UNNotificationAction.init(identifier: "Delete", title: "Delete", options: UNNotificationActionOptions.destructive)
let categories = UNNotificationCategory.init(identifier: "Category", actions: [completeAction, editAction, deleteAction], intentIdentifiers: [], options: [])
centre.setNotificationCategories([categories])
center.add(request, withCompletionHandler: nil)
}
}
}
In this section of code we are adding lines 20 – 25 as well as line 11.
Lines 20 – 22 are used to define what actions need to be made available. We create 3 UNNotificationAction objects here and initialise with an identifier (this is what our switch statement from earlier inspects), a title which is what appears on the button on the notification, and some options which are UNNotificationActionOptions which can be used to request the device is unlocked first with the .authenticationRequired constant specified, or change the text to red if .destructive is selected, and finally, .foreground which opens the app when selected. You do not have to pass constants in here, and if you don’t, the button will just have normal behaviour.
On line 23 we create a UNNotificationCategory. The category comprises of the UNNotificationActions that we created just prior. When we initialise it, it needs an identifier which is used when we are about to send the notification (set also in line 11). It needs the actions in a Set, and we can choose to specify intentIdentifiers as well as options although the latter 2 won’t be used in this tutorial.
On line 25 we set the notificationCategories property. Here, we can add more than 1 category if needed. If you jump back to line 11, this is where we specify what category we want to use for the particular notification being sent. Ideally you would have the categories already set up prior to sending the notifications as adding them inline with the notification being sent is just duplicating something that only needs to be set once in the app. For this reason, you might choose to put the category and associated actions in to an init method, or perhaps shift them over to the app delegate.
Running the app will now allow you to swipe down on a notification and select one of the available options.
You can download the full tutorial here.
In the next tutorial we’ll carry on looking at other aspects of the UserNotifications framework including alternative trigger options.
Silas Caxias says
Nice! Thanks for tutorial.
Ryan Yahya says
very nice thanks