Reputation: 31
I am using macOS 10.5.6 and I am trying to display a custom notification. I am using UNNotificationAction
to set up a drop down menu for the notification and UNNotificationCategory
to save it. I can get the notification correctly. The title and body are displayed but the popup menu for the notification is displayed under a button labeled "Actions".
What I would like to happen is have the label "Actions" changed to a two button format the way that the Reminders app does. I have spent a couple of days searching this web site and several others trying to find the answer but all I have found is the method I am currently using to set up the notification with out the button format that I would like to display. I know that it can be done I just do not know which key words to use to get the answer I would appreciate any help I can get.
Upvotes: 3
Views: 1757
Reputation: 21219
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).
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.
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"
}
}
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))
}
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.
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)
}
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))")
}
}
Following functions are implemented in the sample project AppDelegate
.
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()
}
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])
}
There's another way how to customize the appearance of notifications, but this is not available on the macOS. You have to use attachments.
Upvotes: 4