Swift Notifications — Local, Remote, and In-App NotificationCenter
In this tutorial, you will learn about Swift Notifications. We cover key concepts, practical examples, and best practices to help you master this topic.
Swift notifications encompass both in-app event broadcasting via NotificationCenter and user-facing alerts through UNUserNotificationCenter that can display banners, play sounds, and update badge counts even when the app is closed.
What You'll Learn
- NotificationCenter for in-app event communication
- UNUserNotificationCenter for local notifications
- Remote push notifications setup and handling
- Notification actions and categories
- Permission requests and notification management
- Testing notifications on simulator and device
Why It Matters
Notifications are essential for user engagement and real-time updates. Local notifications remind users of appointments, push notifications deliver breaking news, and in-app notifications coordinate between components. iOS's notification system requires careful permission handling and respects user privacy choices.
Real-World Use
A calendar app sends local notifications for upcoming events, receives push notifications when a meeting is cancelled, uses NotificationCenter to update the UI when data changes, and provides interactive notification actions to snooze or accept meeting invitations directly from the banner.
Learning Path
flowchart LR A[Concurrency
Lesson 22] --> B[Notifications
You are here] B --> C[Combine Framework
Lesson 24] B --> D[SwiftUI Basics
Lesson 25] style B fill:#f90,color:#fff
NotificationCenter (In-App Events)
NotificationCenter enables loosely coupled communication between components within the same app Process.
import Foundation
extension Notification.Name {
static let userDidLogin = Notification.Name("userDidLogin")
static let userDidLogout = Notification.Name("userDidLogout")
static let dataDidUpdate = Notification.Name("dataDidUpdate")
}
class NotificationService {
static let shared = NotificationService()
private let center = NotificationCenter.default
func postLogin(username: String) {
center.post(
name: .userDidLogin,
object: nil,
userInfo: ["username": username, "timestamp": Date()]
)
}
func postLogout() {
center.post(name: .userDidLogout, object: nil)
}
func postDataUpdate() {
center.post(name: .dataDidUpdate, object: nil)
}
}
class ProfileViewController {
private var observers: [NSObjectProtocol] = []
init() {
let loginObserver = NotificationCenter.default.addObserver(
forName: .userDidLogin,
object: nil,
queue: .main
) { notification in
if let username = notification.userInfo?["username"] as? String {
print("Profile: User \(username) logged in")
}
}
observers.append(loginObserver)
let logoutObserver = NotificationCenter.default.addObserver(
forName: .userDidLogout,
object: nil,
queue: .main
) { _ in
print("Profile: User logged out — clearing UI")
}
observers.append(logoutObserver)
}
deinit {
for observer in observers {
NotificationCenter.default.removeObserver(observer)
}
}
}
let profile = ProfileViewController()
NotificationService.shared.postLogin(username: "Alice")
NotificationService.shared.postLogout()
Output:
Profile: User logged in
Profile: User logged out — clearing UI
Always remove observers in deinit to prevent crashes from dangling Observer references.
Local Notifications (UNUserNotificationCenter)
Local notifications are scheduled by the app and delivered by iOS, even when the app is in the background.
import UserNotifications
class LocalNotificationManager {
static let shared = LocalNotificationManager()
private let center = UNUserNotificationCenter.current()
func requestPermission() {
center.requestAuthorization(options: [.alert, .sound, .badge]) { granted, error in
if let error = error {
print("Permission error: \(error.localizedDescription)")
return
}
print("Notification permission \(granted ? "granted" : "denied")")
}
}
func scheduleReminder(title: String, body: String, secondsFromNow: TimeInterval) {
let content = UNMutableNotificationContent()
content.title = title
content.body = body
content.sound = .default
content.badge = 1
let trigger = UNTimeIntervalNotificationTrigger(
timeInterval: secondsFromNow,
repeats: false
)
let request = UNNotificationRequest(
identifier: UUID().uuidString,
content: content,
trigger: trigger
)
center.add(request) { error in
if let error = error {
print("Failed to schedule: \(error.localizedDescription)")
} else {
print("Notification scheduled in \(Int(secondsFromNow))s: \(title)")
}
}
}
func scheduleDailyReminder(title: String, body: String, hour: Int, minute: Int) {
var dateComponents = DateComponents()
dateComponents.hour = hour
dateComponents.minute = minute
let content = UNMutableNotificationContent()
content.title = title
content.body = body
content.sound = .default
let trigger = UNCalendarNotificationTrigger(
dateMatching: dateComponents,
repeats: true
)
let request = UNNotificationRequest(
identifier: "daily_reminder",
content: content,
trigger: trigger
)
center.add(request) { error in
if let error = error {
print("Failed to schedule daily: \(error.localizedDescription)")
} else {
print("Daily reminder set for \(hour):\(minute)")
}
}
}
func removeAllPending() {
center.removeAllPendingNotificationRequests()
center.removeAllDeliveredNotifications()
print("All notifications removed")
}
func getPendingCount() {
center.getPendingNotificationRequests { requests in
print("Pending notifications: \(requests.count)")
}
}
}
let notifManager = LocalNotificationManager.shared
notifManager.requestPermission()
notifManager.scheduleReminder(
title: "Coffee Break",
body: "Time to take a break and grab some coffee!",
secondsFromNow: 10
)
notifManager.scheduleDailyReminder(
title: "Good Morning",
body: "Start your day with a smile!",
hour: 8,
minute: 0
)
Handling Notification Responses
When the user taps a notification, your app handles the response.
import UIKit
class NotificationDelegate: NSObject, UNUserNotificationCenterDelegate {
func userNotificationCenter(
_ center: UNUserNotificationCenter,
willPresent notification: UNNotification,
withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void
) {
// Show banner even when app is in foreground
completionHandler([.banner, .sound, .badge])
}
func userNotificationCenter(
_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: @escaping () -> Void
) {
let userInfo = response.notification.request.content.userInfo
let actionIdentifier = response.actionIdentifier
switch actionIdentifier {
case UNNotificationDefaultActionIdentifier:
print("App opened from notification")
case "SNOOZE_ACTION":
print("Snooze action selected")
case "COMPLETE_ACTION":
print("Complete action selected")
default:
print("Unknown action: \(actionIdentifier)")
}
if let customData = userInfo["customData"] as? String {
print("Custom data: \(customData)")
}
completionHandler()
}
}
// Setup in AppDelegate:
// let delegate = NotificationDelegate()
// UNUserNotificationCenter.current().delegate = delegate
Notification Actions
Add interactive buttons to notifications using UNNotificationCategory.
func registerActions() {
let snoozeAction = UNNotificationAction(
identifier: "SNOOZE_ACTION",
title: "Snooze 5 min",
options: [.authenticationRequired]
)
let completeAction = UNNotificationAction(
identifier: "COMPLETE_ACTION",
title: "Complete",
options: [.foreground]
)
let category = UNNotificationCategory(
identifier: "TASK_REMINDER",
actions: [snoozeAction, completeAction],
intentIdentifiers: [],
options: [.customDismissAction]
)
UNUserNotificationCenter.current()
.setNotificationCategories([category])
print("Notification actions registered")
}
func scheduleActionableReminder() {
let content = UNMutableNotificationContent()
content.title = "Task Reminder"
content.body = "Review the quarterly report"
content.categoryIdentifier = "TASK_REMINDER"
content.userInfo = ["taskID": "12345"]
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 10, repeats: false)
let request = UNNotificationRequest(
identifier: "task_reminder",
content: content,
trigger: trigger
)
UNUserNotificationCenter.current().add(request) { error in
if let error = error {
print("Failed: \(error.localizedDescription)")
} else {
print("Actionable reminder scheduled")
}
}
}
registerActions()
scheduleActionableReminder()
Remote Push Notifications
Remote notifications are sent from a server to Apple Push Notification Service (APNS), which delivers them to the device.
import UIKit
class PushNotificationManager: NSObject, UNUserNotificationCenterDelegate {
func registerForPushNotifications() {
UNUserNotificationCenter.current().delegate = self
UNUserNotificationCenter.current().requestAuthorization(
options: [.alert, .sound, .badge]
) { granted, _ in
guard granted else {
print("Push notification permission denied")
return
}
DispatchQueue.main.async {
UIApplication.shared.registerForRemoteNotifications()
}
}
}
func application(
_ application: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
) {
let tokenParts = deviceToken.map { String(format: "%02.2hhx", $0) }
let token = tokenParts.joined()
print("Device token: \(token)")
// Send token to your server
}
func application(
_ application: UIApplication,
didFailToRegisterForRemoteNotificationsWithError error: Error
) {
print("Push registration failed: \(error.localizedDescription)")
}
func userNotificationCenter(
_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: @escaping () -> Void
) {
let userInfo = response.notification.request.content.userInfo
// Handle push notification tap
if let aps = userInfo["aps"] as? [String: Any] {
print("Push notification tapped: \(aps)")
}
completionHandler()
}
}
// In AppDelegate:
// let pushManager = PushNotificationManager()
// pushManager.registerForPushNotifications()
The device token is unique to the app and device. Send it to your server so it can send targeted push notifications through APNS.
Notification Best Practices
class NotificationBestPractices {
// 1. Check notification settings before scheduling
func checkSettings() {
UNUserNotificationCenter.current().getNotificationSettings { settings in
switch settings.authorizationStatus {
case .authorized, .provisional:
print("Notifications authorized")
case .denied:
print("Notifications denied — prompt user in settings")
case .notDetermined:
print("Not yet asked")
case .ephemeral:
print("Ephemeral authorization")
@unknown default:
break
}
}
}
// 2. Use threadIdentifier for grouping
func scheduleGroupedNotification() {
let content = UNMutableNotificationContent()
content.title = "New message from Alice"
content.body = "Hey, are you free for lunch?"
content.threadIdentifier = "chat_12345"
let request = UNNotificationRequest(
identifier: UUID().uuidString,
content: content,
trigger: nil
)
UNUserNotificationCenter.current().add(request) { _ in
print("Grouped notification added")
}
}
// 3. Implement quiet time
func scheduleWithQuietTime() {
let hour = Calendar.current.component(.hour, from: Date())
guard hour >= 8 && hour < 22 else {
print("Quiet hours — notification suppressed")
return
}
print("Scheduling notification...")
}
}
Common Mistakes
Not requesting permission before scheduling: Notifications fail silently if permission is denied. Always check
authorizationStatusbefore scheduling.Forgetting to set the notification delegate: Without setting
UNUserNotificationCenter.current().delegate, notifications do not show banners when the app is in the foreground.Over-scheduling notifications: iOS limits pending notifications to 64 per app. Exceeding this silently drops requests. Manage pending requests carefully.
Using NotificationCenter without removing observers: This causes crashes when deallocated objects receive notifications. Always remove observers in
deinit.Ignoring the sandbox vs production APNS environment: Development builds use the sandbox APNS environment, TestFlight and App Store builds use production. Server configuration must match.
Practice Questions
- What is the difference between NotificationCenter and UNUserNotificationCenter?
- How many pending notifications can an iOS app schedule simultaneously?
- Why must you remove NotificationCenter observers in deinit?
- What is the role of
threadIdentifierin notification grouping? - Challenge: Build a medication reminder app that schedules daily notifications at configurable times, includes "Taken" and "Snooze" action buttons, groups notifications by medication type, and tracks which doses were taken.
Mini Project
Create a TaskReminderApp with:
- A
Reminderstruct withid,title,date,priority(high, medium, low) - Local notification scheduling for each reminder
- Notification actions: "Complete" (marks done), "Snooze 10 min" (reschedules)
- Category registration with appropriate actions
- A delegate that handles foreground presentation and action responses
- Pending notification count display
FAQ
What's Next
After mastering notifications, explore Reactive Programming with Combine Framework for event streams, or start building modern UIs with SwiftUI Basics.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro