Mac OS X NSUserNotificationCenter notification get dismiss event/callback
Asked Answered
C

4

15

In our app we are displaying Notification Center notifications in alert style.

Displaying notification works fine, as well as we get callback when user interacts with the notification either by clicking on notification or by clicking on Action button.

However, we are interested in getting a callback or event when user clicks on Other button in notification. I have seen MAC OS does this when it displays its updates available dialog.

Refer to this image for clarification about OS X update available alert:

enter image description here

I have searched this over the internet, as well as gone through Notification Center's documentation this and this as well.

Is there any undocumented API? or some custom mechanism for detecting click on Other (close) button?

Cursive answered 14/1, 2014 at 10:6 Comment(0)
A
9

While the other (close) button is clearly meant to dismiss the notification, regardless of what its custom caption may indicate, there is no elegant way to get notified when the user dismisses the notification by clicking on the close button.

What you could do, however, is to monitor the default user notification center's deliveredNotifications property: As long as the notification has not yet been dismissed, the array will contain the notification. Once the notification has been dismissed, the array will not contain it anymore.

This could be implemented in a NSUserNotificationCenter delegate method like this:

- (void)userNotificationCenter:(NSUserNotificationCenter *)center didDeliverNotification:(NSUserNotification *)notification
{
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
                   ^{
                       BOOL notificationStillPresent;
                       do {
                           notificationStillPresent = NO;
                           for (NSUserNotification *nox in [[NSUserNotificationCenter defaultUserNotificationCenter] deliveredNotifications]) {
                               if ([nox.identifier isEqualToString:notification.identifier]) notificationStillPresent = YES;
                           }
                           if (notificationStillPresent) [NSThread sleepForTimeInterval:0.20f];
                       } while (notificationStillPresent);
                       dispatch_async(dispatch_get_main_queue(), ^{
                           [self notificationHandlerForNotification:notification];
                       });
                   });
}

This code will check if the notification is still there every 200 milliseconds. Once it is gone, the -notificationHandler: method will be called on the main thread, which is just an arbitrary callback method.

In this custom -notificationHandler: method you could check whether NSUserNotificationCenter's didActivateNotification: delegate method has been called for the notification. If it hasn't, the user most likely clicked on the close button of the notification.

This is not failsafe, though, as the user may also have otherwise dismissed the notification, i.e. without clicking on the close button.

Abdul answered 26/1, 2014 at 15:28 Comment(8)
I will give your solution a try. However, what is deliveredNotifications property? Can you please link to some documentation? Is this undocumented? I didn't find over the internet.Cursive
Thanks for your help. I am now getting the idea and and I tried your solution. I now also understand deliveredNotifications property. Now what is "identifier"? Are you referring to some custom defined property? that I need to define on this object? XCode says property not found on object NSUserNotification. Sorry, but I am a bit new on this subject.Cursive
Sorry, the identifier property is new in OS X 10.9, and it is not properly documented in the docs, please see the class header file. In short, it is an NSString instance you can use for an arbitrary identifier if you want. From the header file: "This identifier is used to uniquely identify a notification. A notification delivered with the same identifier as an existing notification will replace that notification, rather then display a new one."Abdul
When running OS X 10.8, you'll have to use another approach to compare notifications, e.g. via their userInfo property, where you could also store some unique identifier, but the general idea stays the same.Abdul
Alright userInfo dictionary idea works. Marking this as answer as this really gave me the idea to implement the solution. Thanks.Cursive
It seems like a better idea to compare notifications with isEqual: if ([nox.identifier isEqual:notification]) notificationStillPresent = YES;Expiration
I was unable to get this to work. I changed the if statement to check if the current [[NSUserNotificationCenter defaultUserNotificationCenter] deliveredNotifications].count is greater than the [[NSUserNotificationCenter defaultUserNotificationCenter] deliveredNotifications].count prior to displaying the notification. Works well this way. Thanks!Tripura
@DanielStorm But that does not tell you which notification was removed.Acropetal
B
2

In Swift 3

func userNotificationCenter(_ center: NSUserNotificationCenter, didDismissAlert notification: NSUserNotification) {
        print("dismissed")
    }

This is not part of the NSUserNotificationDelegate but works perfectly

Bluefish answered 23/1, 2017 at 17:7 Comment(1)
@CristiBăluță if you are using swift >=4 make sure you use the @objc annotation; otherwise the objective-C runtime won't be able to see your delegate methodAbreu
H
1

In Swift 2.3:

func userNotificationCenter(center: NSUserNotificationCenter, didDeliverNotification notification: NSUserNotification) {

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) { 
        var notificationStillPresent: Bool
        repeat {
            notificationStillPresent = false
            for nox in NSUserNotificationCenter.defaultUserNotificationCenter().deliveredNotifications {
                if nox.identifier == notification.identifier {
                    notificationStillPresent = true
                    break
                }
            }

            if notificationStillPresent {
                let _ = NSThread.sleepForTimeInterval(0.20)
            }
        } while notificationStillPresent

        dispatch_async(dispatch_get_main_queue()) {
            self.notificationHandlerFor(notification)
        }
    }
}

PS: Please also notice that it is the way to detect the dismiss event, which can be triggered in several cases.

  1. Click the otherButton to dismiss
  2. Click the Clear All button in Notification Center

PS 2: If you are using deliveryRepeatInterval, say 1 minute, there are multiple notifications in deliveredNotifications array, while only one is displayed. The dismissal shall trigger multiple callbacks.

PS 3: Clicking actionButton will also trigger the dismissal callback

Hesterhesther answered 26/10, 2016 at 8:50 Comment(1)
Currently I'm trying to distinguish between a single notification dismissed (user pressed the little X on the top left of the notification) and a "Clear All" action (user clicked the X on a stacked thread of notifications, then on the "Clear All" that appears. TO NO AVAIL. I get the same actionID - and some arbitrary response+request of one of the notifications in the thread. beats me.Escargot
H
1

This helped me out

func userNotificationCenter(_ center: NSUserNotificationCenter, didActivate notification: NSUserNotification) {
    switch (notification.activationType) {
    case .none:
        print("none CLicked")
        break
    case .actionButtonClicked:
        print("Additional Action Clicked")
        break
    case .contentsClicked:
        print("contents CLicked")
        break
    case .replied:
        print("replied Action Clicked")
        break
    case .additionalActionClicked:
        print("Additional  MENU  Action Clicked")
        break
    }
Hannus answered 19/1, 2018 at 15:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.