Sample notifications
A notification with an attachment:

A notification with an attachment, mouse is hovering over to make the action buttons visible (they're visible right away if there's no attachment).

Sample project
Delegate
AppDelegate is going to handle notifications in the following sample project. We have to make it conform to the UNUserNotificationCenterDelegate protocol.
import UserNotifications
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDelegate {
...
}
We have to set the UNUserNotificationCenter.delegate to our AppDelegate in order to receive notifications. It must be done in the applicationDidFinishLaunching: method.
func applicationDidFinishLaunching(_ aNotification: Notification) {
setupNotificationCategories() // See below
UNUserNotificationCenter.current().delegate = self
// Other stuff
}
Authorization, capabilities, ... omitted for simplicity.
Constants
An example how to avoid hardcoded constant.
enum Note {
enum Action: String {
case acceptInvitation = "ACCEPT_INVITATION"
case declineInvitation = "DECLINE_INVITATION"
var title: String {
switch self {
case .acceptInvitation:
return "Accept"
case .declineInvitation:
return "Decline"
}
}
}
enum Category: String, CaseIterable {
case meetingInvitation = "MEETING_INVITATION"
var availableActions: [Action] {
switch self {
case .meetingInvitation:
return [.acceptInvitation, .declineInvitation]
}
}
}
enum UserInfo: String {
case meetingId = "MEETING_ID"
case userId = "USER_ID"
}
}
Setup categories
Make the notification center aware of our custom categories and actions. Call this function in the applicationDidFinishLaunching:.
func setupNotificationCategories() {
let categories: [UNNotificationCategory] = Note.Category.allCases
.map {
let actions = $0.availableActions
.map { UNNotificationAction(identifier: $0.rawValue, title: $0.title, options: [.foreground]) }
return UNNotificationCategory(identifier: $0.rawValue,
actions: actions,
intentIdentifiers: [],
hiddenPreviewsBodyPlaceholder: "",
options: .customDismissAction)
}
UNUserNotificationCenter.current().setNotificationCategories(Set(categories))
}
Create a notification content
Sample notification content with an attachment. If we fail to create an
attachment we will continue without it.
func sampleNotificationContent() -> UNNotificationContent {
let content = UNMutableNotificationContent()
content.title = "Hey Jim! Weekly Staff Meeting"
content.body = "Every Tuesday at 2pm"
content.userInfo = [
Note.UserInfo.meetingId.rawValue: "123",
Note.UserInfo.userId.rawValue: "456"
]
content.categoryIdentifier = Note.Category.meetingInvitation.rawValue
// https://developer.apple.com/documentation/usernotifications/unnotificationattachment/1649987-init
//
// The URL of the file you want to attach to the notification. The URL must be a file
// URL and the file must be readable by the current process. This parameter must not be nil.
//
// IOW We can't use image from the assets catalog. You have to add an image to your project
// as a resource outside of assets catalog.
if let url = Bundle.main.url(forResource: "jim@2x", withExtension: "png"),
let attachment = try? UNNotificationAttachment(identifier: "", url: url, options: nil) {
content.attachments = [attachment]
}
return content
}
Important: you can't use an image from the assets catalog, because you need an URL pointing to a file readable by the current process.

Trigger helper
Helper to create a trigger which will fire a notification in seconds seconds.
func triggerIn(seconds: Int) -> UNNotificationTrigger {
let currentSecond = Calendar.current.component(.second, from: Date())
var dateComponents = DateComponents()
dateComponents.calendar = Calendar.current
dateComponents.second = (currentSecond + seconds) % 60
return UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: false)
}
Notification request
let content = sampleNotificationContent()
let trigger = triggerIn(seconds: 5)
let uuidString = UUID().uuidString
let request = UNNotificationRequest(identifier: uuidString, content: content, trigger: trigger)
UNUserNotificationCenter.current().add(request) { (error) in
if error != nil {
print("Failed to add a notification request: \(String(describing: error))")
}
}
Handle notifications
Following functions are implemented in the sample project AppDelegate.
Background
This is called when your application is in the background (or even if your application is running, see Foreground below).
func userNotificationCenter(_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler:
@escaping () -> Void) {
guard let action = Note.Action(rawValue: response.actionIdentifier) else {
print("Unknown response action: \(response.actionIdentifier)")
completionHandler()
return
}
let userInfo = response.notification.request.content.userInfo
guard let meetingId = userInfo[Note.UserInfo.meetingId.rawValue] as? String,
let userId = userInfo[Note.UserInfo.userId.rawValue] as? String else {
print("Missing or malformed user info: \(userInfo)")
completionHandler()
return
}
print("Notification response: \(action) meetingId: \(meetingId) userId: \(userId)")
completionHandler()
}
Foreground
This is called when the application is in the foreground. You can handle the notification silently or you can just show it (this is what the code below does).
func userNotificationCenter(_ center: UNUserNotificationCenter,
willPresent notification: UNNotification,
withCompletionHandler completionHandler:
@escaping (UNNotificationPresentationOptions) -> Void) {
completionHandler([.alert, .badge, .sound])
}
iOS customization
There's another way how to customize the appearance of notifications, but this is not available on the macOS. You have to use attachments.
