We don't need no stinking badges

October 31, 2016

This is a follow-up to How to Badge an App’s Icon in the Dock by Matthias Gansrigler, or as he's known in the developer community, "Rig". I want to thank Rig for helping me to solve a problem with NSUserNotification. My app posts notifications to Notification Center using -[NSUserNotificationCenter deliverNotification:], and this worked fine for alerts and sounds, but I couldn't figure out how to handle the "Badge app icon" setting in System Preferences, Notifications. The documentation for NSUserNotification is sparse and implies that Notification Center handles everything automatically. The reality is that developers need to do almost everything themselves. Rig's blog post talked about an app that only shows a Dock badge, so I want to expand on that to talk about an app that shows alerts and plays sounds in addition to badging the Dock icon. Here's the code that I use to show an "unread" count:

-(void)userNotificationCenter:(NSUserNotificationCenter *)center didDeliverNotification:(NSUserNotification *)notification
{
	if ([notification isPresented])
		[[NSApp dockTile] setBadgeLabel:[NSString stringWithFormat:@"%lu", ++_badgeCount]];
}

I keep the badge count internally, as an ivar, and I only increment the badge count when a notification is "presented". The presented property of NSUserNotification is generic, so it applies even if the Notifications alert style is set to "None" in System Preferences. When you call -[NSUserNotificationCenter deliverNotification:], you don't know at that point whether the notification will be presented to the user — this usually depends on whether the app is active or not ‐ so you want to set the NSUserNotificationCenter delegate and use the delegate method above to determine whether it is appropriate to increment the badge count.

There is no API to determine whether the user has "Badge app icon" checked in System Preferences. I was stuck on that, but the trick here, which Rig discovered, is that when "Badge app icon" is unchecked, your app's calls to setBadgeLabel: will not actually set the badge label. This is analogous to deliverNotification: in that you should call the method regardless, and Notification Center determines whether anything happens as a result, according to the user's preferences. In hindsight this makes sense, but unfortunately the documentation is sorely lacking to make sense of it with foresight.

If you want to clear the unread count when the app becomes active, you can use the <NSApplicationDelegate> method:

-(void)applicationDidBecomeActive:(NSNotification *)notification
{
	_badgeCount = 0;
	[[NSApp dockTile] setBadgeLabel:nil];
}

You'll also need to call [[NSApp dockTile] setBadgeLabel:nil] in applicationWillTerminate: otherwise the Dock badge will remain visible after the app quits. And you may want to call it in applicationDidFinishLaunching: too, in case your app crashed before it could clear the previous badge.

Before I end this blog post, I'll give you a few more undocumented hints about NSUserNotification usage. Most important, you must set informativeText, otherwise the notification won't be presented, no matter what. You'll also want to set soundName in order to support the "Play sound for notifications" preference. I use NSUserNotificationDefaultSoundName for the value. Again, there's no API to determine the preference setting, but if you set a sound, it will only get played if "Play sound for notifications" is checked. Finally, let's look at what the Human Interface Guidelines have to say about notifications:

Provide a custom message that does not include your app name. Your custom message is displayed in alerts and banners, and in Notification Center list items. You should not include your app’s name in your custom message, because macOS automatically displays the name with your message.

Fact check: This is true, but misleading. The notification title will automatically become the app's name if you don't call -[NSUserNotification setTitle:]. If you set a custom title, then the app's name is not automatically displayed.

On a personal note, I'd like to present a gift to Rig for his help with Dock badging. There won't be any money, but when you die, on your deathbed, you will receive total consciousness. So you got that going for you, which is nice.