Handling User Notifications in SwiftUI

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 UserNotifications framework.
  • The ContentView struct contains a hasNotificationPermission state 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 a UNMutableNotificationContent with a title, body, and sound.
  • A UNTimeIntervalNotificationTrigger is created to trigger the notification after 5 seconds.
  • A UNNotificationRequest is 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, and secondAction. These dictate the user options that will appear on the notification
  • Define the Notification category identifier in the content that links to the identifier
  • Then, configure and set these options and configure these notifications on AppDelegate by adapting to the UIApplicationDelegate by inheriting AppDelegate and confirming with UNUserNotificationCenterDelegate
  • 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.