civil-and-structural-engineering
Using Notifications Framework to Schedule Local Notifications in Ios
Table of Contents
Understanding the iOS Notifications Framework
The User Notifications framework (UserNotifications) provides a unified API for managing local and remote notifications in iOS, macOS, watchOS, and tvOS. Introduced in iOS 10, it replaced the older UILocalNotification system with a more flexible and secure approach. The central class is UNUserNotificationCenter, which handles permission requests, scheduling, and delivery of notification requests. Each notification is represented by a UNNotificationRequest object that combines content (title, body, sound, attachments) with a trigger (time, calendar, or location) and a unique identifier.
Local notifications are entirely generated on the device and do not require a server connection. They are ideal for reminders, alarms, timers, and other time-sensitive alerts. Remote notifications (push notifications) are sent from a server, but both types share the same delivery infrastructure once they reach the device. This article focuses exclusively on scheduling local notifications using the Notifications Framework.
Requesting Notification Permissions
Before scheduling any notification, you must request permission from the user. iOS displays a system prompt, and the user’s response determines whether notifications are allowed. The requestAuthorization(options:) method accepts a bitmask of UNAuthorizationOptions such as .alert, .sound, and .badge. You should request only the options your app genuinely needs; requesting too many may reduce the likelihood of user approval.
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { granted, error in
if granted {
print("User granted notification permission")
} else if let error = error {
print("Error requesting authorization: \(error.localizedDescription)")
} else {
print("User denied permission")
}
}
It is best practice to request authorization at an appropriate time, such as when the user first performs an action that requires notifications (e.g., setting a reminder), rather than on app launch. You can check the current authorization status using getNotificationSettings to determine whether you need to request again. If the user previously denied permission, the system will not prompt again unless the user manually re-enables notifications in Settings. In that case, you can guide users to your app’s settings using UIApplication.openSettingsURLString.
UNUserNotificationCenter.current().getNotificationSettings { settings in
switch settings.authorizationStatus {
case .notDetermined:
// Request authorization
case .denied:
// Direct user to settings
case .authorized, .provisional:
// Can schedule notifications
@unknown default:
break
}
}
Provisional Authorization
iOS 12 introduced provisional authorization, allowing notifications to be delivered quietly (without sound or alert) without explicit user permission. This is useful for apps that provide timely information but do not want to disrupt the user. You can request provisional authorization by including .provisional in the options:
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge, .provisional]) { granted, error in ... }
With provisional authorization, notifications appear in Notification Center but do not trigger sounds or alerts. The user can later upgrade to full authorization, either automatically after receiving a few notifications or by manually enabling in Settings.
Creating a Local Notification Request
A local notification is defined by three components: content, trigger, and identifier. The identifier is a unique string that allows you to update or remove the notification later. The content must be a UNMutableNotificationContent object, which you can customise with a title, subtitle, body, sound, launch image name, badge number, and attachments.
let content = UNMutableNotificationContent()
content.title = "Meeting Reminder"
content.subtitle = "Project Standup"
content.body = "Don't forget the 10 AM standup in conference room B."
content.sound = UNNotificationSound.default
content.badge = 1
Notification Attachments
You can attach images, audio, or video files to local notifications using UNNotificationAttachment. The file must be in the app bundle or a temporary directory. Attachments appear in the notification’s expanded view.
if let imageURL = Bundle.main.url(forResource: "icon_standup", withExtension: "png") {
let attachment = try UNNotificationAttachment(identifier: "image", url: imageURL, options: nil)
content.attachments = [attachment]
}
Understanding Triggers
The trigger determines when the notification will be delivered. iOS provides three types of triggers: time interval, calendar, and location.
Time Interval Trigger
UNTimeIntervalNotificationTrigger fires after a specified number of seconds, optionally repeating. The minimum interval is 60 seconds if repeating; non-repeating triggers can be as low as 1 second. Useful for timers and reminders that occur after a fixed duration.
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 300, repeats: false)
// Fires once after 5 minutes
Calendar Trigger
UNCalendarNotificationTrigger fires on a specific date and time, optionally repeating daily, weekly, monthly, or yearly. Use DateComponents to define the fire schedule.
var dateComponents = DateComponents()
dateComponents.hour = 9
dateComponents.minute = 0
let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: true)
// Fires every day at 9:00 AM
To fire at a specific date, create DateComponents from a Date:
let date = Date(timeIntervalSinceNow: 86400) // tomorrow
let dateComponents = Calendar.current.dateComponents([.year, .month, .day, .hour, .minute], from: date)
let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: false)
Location Trigger
UNLocationNotificationTrigger fires when the device enters or exits a defined geographic region. You need to request location permissions and create a CLCircularRegion. This is powerful for geofence-based reminders (e.g., “Remind me to buy milk when I leave home”).
let center = CLLocationCoordinate2D(latitude: 37.7749, longitude: -122.4194)
let region = CLCircularRegion(center: center, radius: 100, identifier: "SanFrancisco")
region.notifyOnEntry = true
region.notifyOnExit = false
let trigger = UNLocationNotificationTrigger(region: region, repeats: false)
Location triggers require “when in use” or “always” location authorization. Test on real devices as the simulator may not simulate region monitoring.
Scheduling the Notification
After creating the content and trigger, combine them into a UNNotificationRequest and pass it to the shared UNUserNotificationCenter using add(_:withCompletionHandler:). The completion handler receives an optional error; if nil, the scheduling succeeded.
let request = UNNotificationRequest(identifier: "standupReminder", content: content, trigger: trigger)
UNUserNotificationCenter.current().add(request) { error in
if let error = error {
print("Failed to schedule notification: \(error.localizedDescription)")
} else {
print("Notification scheduled successfully")
}
}
You can schedule multiple notifications with different identifiers. The system delivers them independently respecting their triggers. There is no hard limit on the number of pending notification requests, but performance may degrade with thousands.
Managing Scheduled Notifications
To avoid duplicate or stale notifications, you need to manage them. The UNUserNotificationCenter provides methods to get pending, remove, and update notifications.
Fetching Pending Notifications
UNUserNotificationCenter.current().getPendingNotificationRequests { requests in
for request in requests {
print("Pending notification: \(request.identifier)")
}
}
Removing Notifications
Remove pending notifications by identifier or group. Using removePendingNotificationRequests(withIdentifiers:) cancels them before delivery. To remove already delivered notifications from Notification Center, use removeDeliveredNotifications(withIdentifiers:).
UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: ["standupReminder"])
Updating a Notification
To update a scheduled notification, simply schedule a new request with the same identifier. The new request replaces the old one. If you want to cancel and replace, you can remove first and then add.
// Reschedule with new content
let newRequest = UNNotificationRequest(identifier: "standupReminder", content: newContent, trigger: newTrigger)
UNUserNotificationCenter.current().add(newRequest) // replaces the old one if identifier matches
Handling Notification Actions and Categories
Notifications can include custom actions (e.g., “Snooze”, “Mark as Done”) using UNNotificationCategory. Categories group actions and are registered with the shared notification center. This allows users to interact with notifications directly from the banner or lock screen.
let snoozeAction = UNNotificationAction(identifier: "SNOOZE", title: "Snooze 5 min", options: [.foreground])
let dismissAction = UNNotificationAction(identifier: "DISMISS", title: "Dismiss", options: [.destructive])
let category = UNNotificationCategory(identifier: "REMINDER_CATEGORY", actions: [snoozeAction, dismissAction], intentIdentifiers: [], options: [])
UNUserNotificationCenter.current().setNotificationCategories([category])
content.categoryIdentifier = "REMINDER_CATEGORY"
When the user taps an action, the app’s userNotificationCenter(_:didReceive:withCompletionHandler:) delegate method is called, where you can handle the response.
extension AppDelegate: UNUserNotificationCenterDelegate {
func userNotificationCenter(_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: @escaping () -> Void) {
let identifier = response.actionIdentifier
if identifier == "SNOOZE" {
// Schedule a new notification 5 minutes later
}
completionHandler()
}
}
Remember to set the delegate before the app finishes launching.
Testing Local Notifications
Testing local notifications can be done with the iOS simulator or a real device. The simulator supports time interval and calendar triggers but not location triggers (requires real device). For repeat triggers, ensure the minimum interval (60 seconds for repeating time interval). You can also use the UNUserNotificationCenter’s getDeliveredNotifications method to verify delivered notifications.
If a notification does not appear, check the following common issues:
- Permission was not granted or was previously denied.
- The app is in the foreground: use UNUserNotificationCenterDelegate’s willPresent method to show the notification banner while in the foreground.
- The trigger time is in the past: notification won’t fire if the trigger is earlier than now.
- Identifier collision: scheduling with the same identifier effectively replaces the previous one.
- Device is in Do Not Disturb or Focus mode: notifications may be silenced or delayed.
Foreground Presentation
By default, notifications are not displayed when your app is in the foreground. To show them as banners or in Notification Center, implement the userNotificationCenter(_:willPresent:withCompletionHandler:) delegate method.
func userNotificationCenter(_ center: UNUserNotificationCenter,
willPresent notification: UNNotification,
withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
completionHandler([.alert, .sound, .badge])
}
Best Practices for Scheduling Local Notifications
To provide a positive user experience, consider the following:
- Request permission contextually. Ask only when the user intends to set a notification. Explain why you need it in a custom alert before the system prompt.
- Respect the user’s time. Avoid scheduling notifications at times the user may be sleeping (e.g., late night or early morning). Use date components to set appropriate hours.
- Use meaningful content. The title and body should be concise and actionable. Avoid generic messages like “Reminder”.
- Limit repeat intervals. If a notification repeats every minute, the user will likely disable notifications entirely. For repeating reminders, consider at least 15-minute intervals.
- Provide a way to manage notifications. Allow users to view, edit, or delete scheduled notifications within your app’s settings or interface.
- Test with Focus modes. Understand that iOS may delay or silence notifications based on the user’s current Focus. This is beyond your control but important to consider.
- Use unique identifiers wisely. Avoid random identifiers unless you intend to create a new notification each time. Use semantically meaningful identifiers to facilitate updates.
- Handle errors gracefully. If scheduling fails (e.g., due to invalid trigger), log the error and inform the user appropriately.
- Consider battery impact. Location triggers use GPS and can drain battery if used too frequently. Use region monitoring sparingly.
Advanced Topics
Notification Groups and Summaries
iOS 12 and later allow grouping notifications by thread identifier. Notifications with the same threadIdentifier are grouped together in Notification Center. This is useful for conversations or multiple reminders from the same app feature.
content.threadIdentifier = "reminders-group"
Critical Alerts
For time-sensitive and life-critical notifications (e.g., medical alerts), you can request critical alert authorization. Critical alerts override Do Not Disturb and the mute switch. This requires special entitlements from Apple and must be justified in the App Store review.
Conclusion
The Notifications Framework provides a powerful and flexible system for scheduling local notifications in iOS apps. By mastering permission requests, content creation, trigger configuration, and lifecycle management, you can build engaging and respectful user experiences. Remember to always test on actual devices for location triggers and to handle delegate methods for foreground presentation and action handling. With thoughtful design, local notifications can be a valuable tool to keep users informed and on track without being intrusive.
For further reading, consult the Apple User Notifications documentation and the UNUserNotificationCenter reference. A practical tutorial can be found at Ray Wenderlich’s iOS Apprentice.