User notifications are a critical component of modern mobile applications. They keep users informed, engaged, and provide timely updates or reminders. In SwiftUI, handling user notifications is straightforward, leveraging the power of the UserNotifications framework. This post explores how to effectively manage user notifications in your SwiftUI applications, from requesting permissions to scheduling and handling notification actions.
Understanding User Notifications
User notifications are alerts or messages displayed to users even when they are not actively using an app. There are two types of user notifications:
- Local Notifications: Scheduled by the app and delivered on the same device.
- Remote (Push) Notifications: Sent from a server to the user’s device.
In this post, we will focus on local notifications and the basic setup required for push notifications.
Prerequisites
- Xcode (latest version)
- macOS (latest version)
- Basic knowledge of SwiftUI
Step 1: Requesting Notification Permissions
Before scheduling any notifications, you must request permission from the user. This ensures transparency and respects the user’s choice. The UNUserNotificationCenter class is used to manage notifications.
import SwiftUI
import UserNotifications
struct ContentView: View {
@State private var hasNotificationPermission: Bool = false
var body: some View {
VStack {
Text(hasNotificationPermission ? "Notifications are enabled" : "Notifications are disabled")
.padding()
Button("Request Notification Permission") {
requestNotificationPermission()
}
.padding()
}
.onAppear {
checkNotificationPermission()
}
}
func requestNotificationPermission() {
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { granted, error in
if granted {
print("Notification permission granted.")
hasNotificationPermission = true
} else if let error = error {
print("Error requesting permission: (error.localizedDescription)")
hasNotificationPermission = false
} else {
print("Notification permission denied.")
hasNotificationPermission = false
}
}
}
func checkNotificationPermission() {
UNUserNotificationCenter.current().getNotificationSettings { settings in
DispatchQueue.main.async {
hasNotificationPermission = settings.authorizationStatus == .authorized
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Explanation:
- We import
UserNotificationsframework. - The
ContentViewstruct contains ahasNotificationPermissionstate variable to track whether the user has granted permission. - The
requestNotificationPermission()function requests authorization with options for alert, badge, and sound. - The
checkNotificationPermission()function checks the current notification settings when the view appears and updates the state accordingly.
Step 2: Scheduling a Local Notification
Once permission is granted, you can schedule local notifications. This involves creating a UNMutableNotificationContent object and a UNNotificationRequest object.
import SwiftUI
import UserNotifications
struct ContentView: View {
@State private var hasNotificationPermission: Bool = false
var body: some View {
VStack {
Text(hasNotificationPermission ? "Notifications are enabled" : "Notifications are disabled")
.padding()
Button("Request Notification Permission") {
requestNotificationPermission()
}
.padding()
Button("Schedule Notification") {
scheduleNotification()
}
.padding()
}
.onAppear {
checkNotificationPermission()
}
}
func requestNotificationPermission() {
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { granted, error in
if granted {
print("Notification permission granted.")
hasNotificationPermission = true
} else if let error = error {
print("Error requesting permission: (error.localizedDescription)")
hasNotificationPermission = false
} else {
print("Notification permission denied.")
hasNotificationPermission = false
}
}
}
func checkNotificationPermission() {
UNUserNotificationCenter.current().getNotificationSettings { settings in
DispatchQueue.main.async {
hasNotificationPermission = settings.authorizationStatus == .authorized
}
}
}
func scheduleNotification() {
let content = UNMutableNotificationContent()
content.title = "SwiftUI Notification"
content.body = "This is a local notification from SwiftUI!"
content.sound = UNNotificationSound.default
// Trigger the notification after 5 seconds
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 5, repeats: false)
let request = UNNotificationRequest(identifier: "localNotification", content: content, trigger: trigger)
UNUserNotificationCenter.current().add(request) { error in
if let error = error {
print("Error scheduling notification: (error.localizedDescription)")
} else {
print("Notification scheduled successfully!")
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Explanation:
- The
scheduleNotification()function creates aUNMutableNotificationContentwith a title, body, and sound. - A
UNTimeIntervalNotificationTriggeris created to trigger the notification after 5 seconds. - A
UNNotificationRequestis created with a unique identifier, content, and trigger. - The notification is added to the
UNUserNotificationCenter.
Step 3: Handling Notification Actions
You can add custom actions to your notifications, allowing users to interact with the notification directly. First, define a notification category and actions, then set them in your notification content.
import SwiftUI
import UserNotifications
struct ContentView: View {
@State private var hasNotificationPermission: Bool = false
var body: some View {
VStack {
Text(hasNotificationPermission ? "Notifications are enabled" : "Notifications are disabled")
.padding()
Button("Request Notification Permission") {
requestNotificationPermission()
}
.padding()
Button("Schedule Notification") {
scheduleNotification()
}
.padding()
}
.onAppear {
checkNotificationPermission()
}
.onReceive(NotificationCenter.default.publisher(for: Notification.Name("NotificationActionReceived"))) { notification in
if let action = notification.object as? String {
print("Received action: (action)")
// Handle the action here (e.g., navigate to a specific view)
}
}
}
func requestNotificationPermission() {
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { granted, error in
if granted {
print("Notification permission granted.")
hasNotificationPermission = true
defineNotificationActions()
} else if let error = error {
print("Error requesting permission: (error.localizedDescription)")
hasNotificationPermission = false
} else {
print("Notification permission denied.")
hasNotificationPermission = false
}
}
}
func checkNotificationPermission() {
UNUserNotificationCenter.current().getNotificationSettings { settings in
DispatchQueue.main.async {
hasNotificationPermission = settings.authorizationStatus == .authorized
}
}
}
func scheduleNotification() {
let content = UNMutableNotificationContent()
content.title = "SwiftUI Notification with Actions"
content.body = "Tap an action!"
content.sound = UNNotificationSound.default
content.categoryIdentifier = "notificationCategory" // Set the category identifier
// Trigger the notification after 5 seconds
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 5, repeats: false)
let request = UNNotificationRequest(identifier: "notificationWithActions", content: content, trigger: trigger)
UNUserNotificationCenter.current().add(request) { error in
if let error = error {
print("Error scheduling notification: (error.localizedDescription)")
} else {
print("Notification scheduled successfully!")
}
}
}
func defineNotificationActions() {
// Define actions
let firstAction = UNNotificationAction(identifier: "firstAction", title: "First Action", options: [])
let secondAction = UNNotificationAction(identifier: "secondAction", title: "Second Action", options: [.destructive])
// Define category
let category = UNNotificationCategory(identifier: "notificationCategory", actions: [firstAction, secondAction], intentIdentifiers: [], options: [])
// Register category
UNUserNotificationCenter.current().setNotificationCategories([category])
}
}
// AppDelegate (or a separate class)
class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
UNUserNotificationCenter.current().delegate = self
return true
}
// Handle notification response
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
let actionIdentifier = response.actionIdentifier
// Post a notification with the action identifier
NotificationCenter.default.post(name: Notification.Name("NotificationActionReceived"), object: actionIdentifier)
completionHandler()
}
// Handle when a notification is presented while the app is in the foreground
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
completionHandler([.banner, .sound, .badge]) // or any combination of options
}
}
In @main of your SwiftUI app, use @UIApplicationDelegateAdaptor
import SwiftUI
@main
struct YourAppName: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
Explanation:
- First you define actions such as
firstAction, andsecondAction. These dictate the user options that will appear on the notification - Define the Notification category identifier in the
contentthat links to the identifier - Then, configure and set these options and configure these notifications on AppDelegate by adapting to the
UIApplicationDelegateby inheritingAppDelegateand confirming withUNUserNotificationCenterDelegate - Using these settings you can override settings in AppDelegate, to override user interactions using the identifier, such as
userNotificationCenter(_:didReceive:withCompletionHandler:), where a posted Notification is being invoked using NotificationCentre when a new NotificationAction is triggered.
Step 4: Handling Remote (Push) Notifications
Handling push notifications involves setting up your app to receive notifications sent from a server. Although this setup is more complex than local notifications, the basic steps include:
- Registering for remote notifications in
AppDelegate. - Handling the device token and sending it to your server.
- Handling incoming push notifications.
Step 4.1: Registering for Remote Notifications
In your AppDelegate, register for remote notifications:
//AppDelegate.swift
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
UNUserNotificationCenter.current().delegate = self // Ensure delegate is set
application.registerForRemoteNotifications()
return true
}
Step 4.2: Handling the Device Token
Implement the following methods in your AppDelegate to handle the device token:
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
let tokenString = deviceToken.map { String(format: "%02.2hhx", $0) }.joined()
print("Device Token: (tokenString)")
// Send this token to your server
}
func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
print("Failed to register for remote notifications: (error.localizedDescription)")
}
Step 4.3: Handling Incoming Push Notifications
Handle incoming push notifications in the userNotificationCenter(_:didReceive:withCompletionHandler:) method, similar to handling notification actions:
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
let userInfo = response.notification.request.content.userInfo
if let message = userInfo["message"] as? String {
print("Received push notification with message: (message)")
// Handle the push notification data
}
completionHandler()
}
Conclusion
Handling user notifications in SwiftUI allows you to create engaging and informative user experiences. From requesting permissions to scheduling local notifications and handling actions, the UserNotifications framework provides powerful tools to manage notifications effectively. By following the steps outlined in this guide, you can integrate notifications seamlessly into your SwiftUI applications and keep your users connected and informed.