Local Notifications: Getting Started
Learn how to create notifications by time intervals, time of day and location, as well as how to support category grouping and prompting for action.
Version
- Swift 5, iOS 14, Xcode 12

Notifications are an integral way of conveying information to the user outside of an app. They help draw users’ attention and drive their engagement with the app. But a burst of notifications can annoy users, causing them to turn them off or even delete the app. So you must use notifications wisely.
There are two types of notifications: local and remote. This tutorial is all about local notifications. In this tutorial, you’ll learn how to:
- Request permission for notifications.
- Create, schedule and manage different types of local notifications.
- Handle notification actions.
Often, users forget about tasks and need a reminder. You’ll make it easy to remind them using the power of local notifications.
Getting Started
Download the project by clicking the Download Materials button at the top or bottom of this page. Open the OrganizerPlus project. Build and run.
OrganizerPlus helps keep track of your tasks. Tap the + button to start creating a task. Next, add a name for the task and tap Save. You can mark the task as completed by tapping the circle next to the task.
Introducing Notifications
Notifications can be either local or remote. The app on the device schedules and configures local notifications. In contrast, a server sends remote notifications using Apple Push Notification Service (APNS). A news alert is an example of a remote notification where the server sends the latest breaking news to the device as a notification.
Remote notifications can be both visible and silent. Silent notifications launch the app in the background to perform an action, such as refreshing the app. You can configure both local and remote notifications using the UserNotifications framework. To learn more about remote notifications, check out Push Notifications Tutorial: Getting Started.
To create and manage notifications, you need the following steps:
- Authorization
- Content creation
- Scheduling the notifications
- Managing the notifications
- Handling actions
You’ll learn how to do each of these steps in this tutorial.
Requesting Notification Permission
Notifications interrupt the user, so an app needs the user to allow them. In OrganizerPlus, you’ll request notification permission when the user taps the bell button at the top right.
First, open NotificationManager.swift. Then, add the following to NotificationManager
:
func requestAuthorization(completion: @escaping (Bool) -> Void) {
UNUserNotificationCenter.current()
.requestAuthorization(options: [.alert, .sound, .badge]) { granted, _ in
// TODO: Fetch notification settings
completion(granted)
}
}
Here’s what the code does:
-
UNUserNotificationCenter
handles all notification-related behavior in the app. This includes requesting authorization, scheduling delivery and handling actions. You can access the shared instance ofUNUserNotificationCenter
by callingcurrent()
. - You invoke
requestAuthorization(options:completionHandler:)
to request authorization to show notifications.options
denotes the notification’s behavior, such as displaying an alert, playing a sound or updating the app’s badge. You can learn more about the variousUNAuthorizationOptions
in the developer documentation. - The completion handler receives a Boolean that indicates whether the user granted the authorization. Here, you call the completion handler with the Boolean value. The
TODO
comment is a placeholder to fetch notification settings. You’ll implement this next.
Now, add the following in NotificationManager
:
func fetchNotificationSettings() {
// 1
UNUserNotificationCenter.current().getNotificationSettings { settings in
// 2
DispatchQueue.main.async {
self.settings = settings
}
}
}
Here’s what you added:
-
UNUserNotificationCenter
‘sgetNotificationSettings(completionHandler:)
requests the notification settings authorized by the app. The settings return asynchronously.UNNotificationSettings
manages all the notification-related settings and the authorization status of the app.settings
is an instance of it. - The completion block may be called on a background thread. Here, you update the settings property on the main thread as changing its value updates the UI.
Now, replace // TODO: Fetch notification settings
in requestAuthorization(completion:)
with the following:
self.fetchNotificationSettings()
Here, you fetch the notification settings after the user has granted the authorization.
Prompting Notification Authorization
Open TaskListView.swift. Then, find // TODO: Add Notification authorization
in the action closure of the button and replace it with the following:
// 1
NotificationManager.shared.requestAuthorization { granted in
// 2
if granted {
showNotificationSettingsUI = true
}
}
Here’s a breakdown:
- You request the notification authorization by calling
requestAuthorization(completion:)
, which you defined inNotificationManager
. - If the user granted permission, you set
showNotificationSettingsUI
totrue
. This presentsNotificationSettingsView
as a sheet.
Build and run.
Tap the bell icon at the top right. This prompts the notification authorization. Next, select Allow. You’ll see the list of notification settings granted by the app.
Nice job getting through the first step of notification management!
Understanding Critical Notifications
In the notification settings, notice that the app doesn’t have authorization for critical alerts. But what are critical alerts?
Critical alerts are health, medical, security or public safety notifications. They bypass the do-not-disturb and ringer switch and play a sound. These are quite disruptive, so not all apps can send critical alerts.
If your app needs to send them, you can apply for entitlement on the Apple developer portal.
Reminders from OrganizerPlus are important but not critical, so you don’t need to enable critical alerts for this tutorial. :]
Now, it’s time to create and schedule notifications.
Creating Notifications
You can create and schedule local notifications using any of the following triggers:
- Time interval
- Calendar
- Location
These options determine when your app delivers the notification. For your next step, you’ll learn to create notifications using each of these triggers.
Triggering TimeInterval Notifications
Open NotificationManager.swift and add the following to NotificationManager
:
// 1
func scheduleNotification(task: Task) {
// 2
let content = UNMutableNotificationContent()
content.title = task.name
content.body = "Gentle reminder for your task!"
// 3
var trigger: UNNotificationTrigger?
switch task.reminder.reminderType {
case .time:
if let timeInterval = task.reminder.timeInterval {
trigger = UNTimeIntervalNotificationTrigger(
timeInterval: timeInterval,
repeats: task.reminder.repeats)
}
default:
return
}
// 4
if let trigger = trigger {
let request = UNNotificationRequest(
identifier: task.id,
content: content,
trigger: trigger)
// 5
UNUserNotificationCenter.current().add(request) { error in
if let error = error {
print(error)
}
}
}
}
Here’s what you added:
-
scheduleNotification(task:)
takes in a parameter of typeTask
. That’s your model, which holds all the data related to any task. It’s defined in Task.swift. - You start creating a notification by populating the notification content.
UNMutableNotificationContent
holds the payload for a local notification. Here, you populate thetitle
andbody
of the notification.UNMutableNotificationContent
has other properties, such as:- subtitle: A secondary description for the notification.
- badge: The number to display for the app’s icon badge.
- sound: The sound to play during notification delivery.
- attachments: An array of attachments to display in the notification. These could be images, video or audio files.
-
UNNotificationTrigger
is an abstract class that triggers the delivery of a notification. Here, you check if thereminderType
of the task is time-based with a valid time interval. Then, you create a time interval-based notification trigger usingUNTimeIntervalNotificationTrigger
. You use this type of trigger to schedule timers. In addition to taking atimeInterval
, the constructor takes in a Boolean parameter,repeats
. This determines whether the notification needs to reschedule after being delivered. You’ll handle the other cases of theswitch
later in this tutorial. - After trigger definition, the next step is to create a notification request. You create a new request using
UNNotificationRequest
and specifying anidentifier
,content
and atrigger
. Each task you create already has a unique identifier. You’ll pass that as the notification identifier. - Then, you schedule the notification by adding the request to
UNUserNotificationCenter
. The completion handler has anerror
object that indicates if a problem occurred when scheduling a notification.
Scheduling the Notification
Open TaskManager.swift. Add the following to save(task:)
after the DispatchQueue.global().async
block and before the method ends:
if task.reminderEnabled {
NotificationManager.shared.scheduleNotification(task: task)
}
Here, you schedule a notification if the task has reminderEnabled
set to true
.
Build and run.
Follow these steps to add a task and test your notification:
- Tap the + button to start adding a task.
- Give the task a name.
- Toggle the Add Reminder switch.
- Use the default selection Time with the Time Interval set to one minute.
- Leave Repeat Notification off.
- Tap Save at the top right to create the task.
Finally, background the app and wait for one minute. You’ll see the notification appear.
Congratulations on your first notification! :]
To view this notification, you had to background the app and wait. Wouldn’t it be cool to view the notification while the app is still in the foreground? You’ll do that next.
Viewing Notification When the App Is in the Foreground
Open AppDelegate.swift and add the following to the end of the file:
// MARK: - UNUserNotificationCenterDelegate
extension AppDelegate: UNUserNotificationCenterDelegate {
func userNotificationCenter(
_ center: UNUserNotificationCenter,
willPresent notification: UNNotification,
withCompletionHandler completionHandler: (UNNotificationPresentationOptions) -> Void
) {
completionHandler(.banner)
}
}
userNotificationCenter(_:willPresent:withCompletionHandler:)
asks the delegate how to handle a notification when the app is in the foreground. Here, you call the completion handler with the presentation option set to a banner
. You can also add other options, such as sound
and badge
.
Next, add the following to AppDelegate
:
private func configureUserNotifications() {
UNUserNotificationCenter.current().delegate = self
}
Here, you declare configureUserNotifications()
, which makes AppDelegate
the delegate for UNUserNotificationCenter
.
Now, add the following to application(_:didFinishLaunchingWithOptions:)
before returning true
:
configureUserNotifications()
Here, you call configureUserNotifications()
to set the notification delegate as soon as the app launches.
Build and run.
To try it out, add a task with a time interval set to one minute and leave the app in the foreground. You’ll see a notification appear while the app is in the foreground. Great job!
Marking a task done deletes it, but what about the notification? You don’t want to see a reminder for a task that isn’t present. You’ll work on that next.
Removing Scheduled Notifications
Open NotificationManager.swift and add the following to NotificationManager
:
func removeScheduledNotification(task: Task) {
UNUserNotificationCenter.current()
.removePendingNotificationRequests(withIdentifiers: [task.id])
}
You need to ensure the removal of any pending notification upon task completion. Do this by using removePendingNotificationRequests(withIdentifers:)
. Here, you pass the identifier of the task in an array. This is especially useful for tasks that have repeats
set to true
.
Next, open TaskManager.swift and add the following to remove(task:)
before returning:
if task.reminderEnabled {
NotificationManager.shared.removeScheduledNotification(task: task)
}
This removes any scheduled notification once the task finishes.
Build and run.
Give this a try by creating a task with a time interval of one minute. This time, toggle the Repeat Notification switch to enable it. You’ll notice the notification appears every minute. Next, mark the created task complete by tapping the circle button next to the task. This will delete the task and you’ll no longer see the scheduled notifications for this task.
You can use removeAllPendingNotificationRequests()
on UNUserNotificationCenter
to unschedule all the pending notifications. removeAllDeliveredNotifications()
will remove all the delivered notifications.
The time interval is just one way to trigger a notification. There are other ways to trigger notifications locally: calendar and location. You’ll implement these next.
Triggering Calendar Notifications
Open NotificationManager.swift. Then, add the following to scheduleNotification(task:)
as an additional case in switch task.reminder.reminderType
:
case .calendar:
if let date = task.reminder.date {
trigger = UNCalendarNotificationTrigger(
dateMatching: Calendar.current.dateComponents(
[.day, .month, .year, .hour, .minute],
from: date),
repeats: task.reminder.repeats)
}
With the code you added, you:
- Check if the
reminder
of thetask
has adate
set. - Create a notification trigger of type
UNCalendarNotificationTrigger
. The calendar trigger delivers a notification based on a particular date and time. It extractsdateComponents
from the date the user selected. Specifying only the time components will trigger a notification at that specified time.
Build and run.
Follow these steps to create a task:
- Tap the + button.
- Give the task a name.
- Toggle the Add Reminder switch.
- Tap the Date trigger in the segment bar.
- Select a particular date and time — maybe one minute from now.
- Leave Repeat Notification off.
- Tap the Save button to create the task.
You’ll see the notification appear. Congratulations on your first calendar notification!
Calendar notifications let you schedule a recurring reminder of a daily routine, such as drinking water every morning.
Now, it’s time to add a reminder to buy GME stock when you pass a GameStop store. :] Enter location-based reminders!
Triggering Location Notifications
The location-based trigger causes the app to deliver a notification when the user enters or exits a region. Behind the scenes, iOS uses geofences for region monitoring. As such, the system limits the number of location-based triggers that the user can schedule at the same time.
The first step to adding a location-based notification is to request location permission. Because the system monitors the regions, the app needs the When-In-Use authorization. The location authorization and delegate code already exist for you in LocationManager.swift.
Open CreateTaskView.swift. Then, find and replace // TODO: Request location authorization
with the following:
locationManager.requestAuthorization()
Here, you request location authorization when the user taps the Request Location Authorization button — the user hasn’t already granted authorization.
Creating a Location Trigger
Open NotificationManager.swift. Next, add the following to scheduleNotification(task:)
by replacing the default
case in switch task.reminder.reminderType
:
case .location:
// 1
guard CLLocationManager().authorizationStatus == .authorizedWhenInUse else {
return
}
// 2
if let location = task.reminder.location {
// 3
let center = CLLocationCoordinate2D(
latitude: location.latitude,
longitude: location.longitude)
let region = CLCircularRegion(
center: center,
radius: location.radius,
identifier: task.id)
trigger = UNLocationNotificationTrigger(
region: region,
repeats: task.reminder.repeats)
}
Here’s a step-by-step breakdown:
- You check if the user has granted at least When In Use location authorization.
- Then, you check to make sure the location data exists for the task reminder.
- You create location-based triggers using
UNLocationNotificationTrigger
. First, you define a center denoted byCLLocationCoordiante2D
. Using this, you create an instance ofCLCircularRegion
by specifying the radius and a unique identifier. Finally, you create the trigger using this circular region. You can specify whether to trigger the notification only when entering the region or when exiting the region by usingnotifyOnEntry
andnotifyOnExit
onCLCircularRegion
.
Build and run.
Tap the + button to start adding a task. Next, tap Location in the segment bar. Then, tap Request Location Authorization if you haven’t granted location authorization already. Now, select Allow While Using App in the location permission prompt. You’ll see Latitude, Longitude and Radius text fields appear.
Enter the latitude and longitude of a place you plan to visit soon or the coordinates of your home. Enter 500 for the radius. This value is in meters. Then, tap Save to create the task.
You’ll now receive a notification when you enter or exit the location you specified. To simulate this, go to the simulator Features menu, select Location ▸ Custom Location… and enter the same latitude and longitude values at the prompt. You’ll see the notification appear from your app in the background. Way to go!
So far, you’ve created notifications based on different triggers. But it would be cool to group these notifications based on the trigger, right? You’ll do that next.
Grouping Notifications
By default, you group all an app’s notifications based on the app’s bundle identifier. However, you can create custom groups for your app. A good example of this is a messaging app that groups the notifications for different message groups.
For OrganizerPlus, you’ll group notifications based on the trigger type.
Open NotificationManager.swift and add the following before NotificationManager
:
enum NotificationManagerConstants {
static let timeBasedNotificationThreadId =
"TimeBasedNotificationThreadId"
static let calendarBasedNotificationThreadId =
"CalendarBasedNotificationThreadId"
static let locationBasedNotificationThreadId =
"LocationBasedNotificationThreadId"
}
Here, you define the constants you’ll use to group the notifications based on the notification trigger type.
Find switch task.reminder.reminderType
in scheduleNotification(task:)
. Add the identifier for each case by setting threadIdentifier
for the notification content, as shown below:
switch task.reminder.reminderType {
case .time:
content.threadIdentifier =
NotificationManagerConstants.timeBasedNotificationThreadId
// ...
case .calendar:
content.threadIdentifier =
NotificationManagerConstants.calendarBasedNotificationThreadId
// ...
case .location:
content.threadIdentifier =
NotificationManagerConstants.locationBasedNotificationThreadId
// ...
}
threadIdentifier
helps group related notifications. Here, you set the identifier for the notification content based on reminderType
.
Build and run.
Create a few tasks with different triggers. When the notifications appear, you’ll see them grouped based on the type of trigger.
You can choose to turn off notification grouping for an app. To do so:
- Go to Settings ▸ Select the app.
- Tap Notifications.
- Tap Notification Grouping.
- Set grouping to Off.
This turns off grouping. The notifications will appear in the order they’re received.
Tapping a notification opens the app. Is there a way to act on a notification without opening the app? You’ll explore that next.
Handling Notification Actions
Actionable notifications allow the user to perform actions on a notification without opening the app. An actionable notification can display one or more buttons in addition to the content.
To support actionable notifications, you’ll need to:
- Declare the notification category at the launch of the app.
- Create and assign actions to the notification category.
- Assign the category identifiers to the notification payload.
- Handle the registered actions.
You’ll implement each of these next.
Declaring Category and Creating Actions
Open AppDelegate.swift. Then, add the following to configureUserNotifications()
below UNUserNotificationCenter.current().delegate = self
:
// 1
let dismissAction = UNNotificationAction(
identifier: "dismiss",
title: "Dismiss",
options: [])
let markAsDone = UNNotificationAction(
identifier: "markAsDone",
title: "Mark As Done",
options: [])
// 2
let category = UNNotificationCategory(
identifier: "OrganizerPlusCategory",
actions: [dismissAction, markAsDone],
intentIdentifiers: [],
options: [])
// 3
UNUserNotificationCenter.current().setNotificationCategories([category])
Here’s a step-by-step breakdown:
-
You declare two actions:
dismissAction
andmarkAsDone
. These will display as action buttons with the notification. Each instance ofUNNotificationAction
has an identifier, title and array of options. Theidentifier
uniquely identifies the action. Thetitle
represents the text on the button.options
denotes the behavior associated with the action. The different options are:- authenticationRequired: A user can perform this action only when the device is unlocked. If the device is locked, the system prompts the user to unlock it.
- destructive: This action performs a destructive task. The button for this option displays with special highlighting.
- foreground: This option causes the app to launch in the foreground.
- Here, you define a notification category.
UNNotificationCategory
defines the type of notifications an app can receive.identifier
uniquely identifies the category.intentIdentifiers
let the system know that the notification relates to a request made by Siri. The options denote how to handle the notifications associated with them. You can learn more about them Apple developer documentation. - Here, you register the new actionable notification.
Now the system knows about your notification category and which actions it has, but an app may have more than one category for notifications and not every category will have actions assigned to it. To let the system know that it should show your actions, you must assign your notifications to this category.
Assigning Identifiers to Notification Payload
Open NotificationManager.swift. Then, add the following in scheduleNotification(task:)
below content.body = "Gentle reminder for your task!"
:
content.categoryIdentifier = "OrganizerPlusCategory"
let taskData = try? JSONEncoder().encode(task)
if let taskData = taskData {
content.userInfo = ["Task": taskData]
}
Here, you set the notification content’s categoryIdentifier
to the identifier you used when you created the instance of UNNotificationCategory
. Then, you encode the task data and assign it to the notification content’s userInfo
. The app will be able to access this content when a user acts on the notification.
Handling Actions
Open AppDelegate.swift. Add the following to the AppDelegate
extension:
// 1
func userNotificationCenter(
_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: @escaping () -> Void
) {
// 2
if response.actionIdentifier == "markAsDone" {
let userInfo = response.notification.request.content.userInfo
if let taskData = userInfo["Task"] as? Data {
if let task = try? JSONDecoder().decode(Task.self, from: taskData) {
// 3
TaskManager.shared.remove(task: task)
}
}
}
completionHandler()
}
Here’s a code breakdown:
- iOS calls
userNotificationCenter(_:didReceive:withCompletionHandler:)
when the user acts on the notification. - Check if the response’s
actionIdentifier
is set tomarkAsDone
. Then, you decode the task fromuserInfo
. - After the decode succeeds, you remove the task using the shared instance of the
TaskManager
.
Build and run.
Create a task by selecting the time trigger. Long-press the notification when it appears. You’ll see two buttons with the notification. Tap the Mark as Done button. The task is removed without opening the app.
Great job! You’ve now become a pro in local notifications.
Where to Go From Here?
Download the project by clicking the Download Materials button at the top or bottom of this page.
In this tutorial, you’ve learned to create, schedule, group and handle local notifications. You can customize the appearance of notifications using a Notification Content Extension. Push Notifications Tutorial for iOS: Rich Push Notifications covers that in detail.
I hope you’ve enjoyed this tutorial. If you have any questions or comments, please join the forum discussion below.
Comments