Displaying user notifications in your RubyMotion OS X app Nov 27 2013

Notification Center was first announced for Mac OS X 10.8 Mountain Lion, and then it saw some improvements for Mac OS X 10.9 Mavericks. Similar to the notifications on iOS, it provides a convenient way for applications to show information to the user, and is especially useful for applications that run in the background, or that have a background processing component that needs to display and highlight information to the user.

Notify

Using them is fairly straightfward - we'll run through how to add them to an application, and we're going to build upon the small status bar app we created in the last tutorial to demo this. If you weren't following along with that article, it ran through creating a status bar application which talked to a JSON API (the GitHub API) to retrieve commit data and show the latest commits from repositories you have access too. We can now extend that to popup a notification whenever new commits are found. We can also respond to a click on the notification by showing the menu containing the list of commits. All of the changes we'll be making are to the app_delegate.rb file.

First things first, we need to add something to the end of our applicationDidFinishLaunching method:

NSUserNotificationCenter.defaultUserNotificationCenter.setDelegate(self)

This will allow us to intercept events regarding our notifications, and handle the clicks on them, which we'll cover shortly.

Next up, we'll add a method to show new commits as a notification:

def showNotification(new_commits)
  return unless new_commits > 0
  notification = NSUserNotification.alloc.init
  notification.title = "New commits"
  notification.informativeText = "There are #{new_commits} new commits."
  notification.soundName = NSUserNotificationDefaultSoundName
  NSUserNotificationCenter.defaultUserNotificationCenter.scheduleNotification(notification)
end

Here we're making sure we only trigger a new notification when there are new commits, we're setting the title and text, and we're using a default sound (the very familiar tri-tone noise). You can also specify a path to a .caf audio file in the resources bundle of the app, if you so wish. Lastly, we schedule the notification with the user notification center, and as we set no particular scheduled date for the notification, it shows immediately. You could schedule a notification for a specific time by setting the deliveryDate on the notification before adding to the user notification center - this one is scheduled for 10 minutes from now:

notification.deliveryDate = Time.now + (10 * 60)
NSUserNotificationCenter.defaultUserNotificationCenter.scheduleNotification(notification)

Now we need to trigger our notifications method, so we're going to dip into our checkForNewEvents method, which is where our commit processing happened, and towards the end where we used to have the following:

@unseen += (@commits - @last_commits).length
self.showUnseenCommits

We'll modify that to this:

new_commits = (@commits - @last_commits).length
@unseen += new_commits
self.showUnseenCommits
self.showNotification(new_commits)

What we're doing here is taking the amount of new commits we found (the difference between our @commits and @last_commits arrays), and we're adding it to the unseen counter to display the total number of unseen commits on the menu, as before. We're then however using just the amount of new commits for this particular call, to display the notification in the user notification center. Essentially the menu bar item itself tracks a running tally of unseen commits, but the notifications should only ever let you know of new ones it has found since the last time it showed a notification.

If you run the app now, you'll see that as soon as it starts up and pulls back the initial list of commits, it triggers a notification! However if we click on it in the user notification center, nothing happens. Ideally, we want it to trigger something to show in our app, and also to remove the notification as it has now been seen and processed by the user.

As we've already setup our AppDelegate as a delegate for the user notification center in applicationDidFinishLaunching, all that remains is to handle the approriate event by creating a method as follows:

def userNotificationCenter(center, didActivateNotification: notification)
  @status_item.popUpStatusItemMenu(@status_menu)
  center.removeDeliveredNotification(notification)
end

The didActivateNotification method is what handles the click on a notification created by our app, and so we perform two functions here - the first is to trigger the status bar menu to popup, showing the latest commits, and the second is to remove the delivered notification from the notification center, thereby clearing it out of the users way.

Notified

That's it, a fairly short tutorial this time around, mainly because the NSUserNotification and NSUserNotificationCenter APIs are very clean, tidy and easy to work with. They provide some great additional interaction with your users, making apps more interesting, informative and accessible - and all in just a few lines of code! You can see the above changes in a commit to our existing codebase here, and you can of course grab the entire code for the app here.

If you have any comments, questions or suggestions, please let me know below. Remember to follow @ejdraper on Twitter to hear about the latest articles as we put them up!

technicalrubymotioncodekickcode