I build web, mobile and desktop apps, produce screencasts, write ebooks, and provide pairing and training

How to quickly get started creating status bar apps for Mac OS X using RubyMotion

Oct 18, 2013 - Elliott Draper

Recently here at KickCode we’ve been working on our first Mac OS X application, due for submission to the App Store shortly, and using RubyMotion. We’ll be unveiling it shortly, but it’s a fairly simple utility app that lives in the status bar. Status bar apps are quite useful, whethere it’s a utility for day to day productivity tasks, or whether it’s a connection or window into an online service, that provides background syncing, notifications, or other information.

New gem

The good news is, they’re fairly simple to get up and running using RubyMotion! Indeed, as with a lot of work in RubyMotion, it’s a lot less effort than building the equivalent in Xcode and Objective-C! And we’ve just made it a bit easier still, with a small gem called osx-status-bar-app-template that provides you with a new RubyMotion app template.

Simply run:

  gem install osx-status-bar-app-template

And then once installed, you can go ahead and create your new RubyMotion app using the template:

  motion create --template=osx-status-bar-app TestStatusBarApp

Then simply fire it up to see the basic skeleton app in action, ready for building upon:

  cd ./TestStatusBarApp
  rake

At this point you’ll see an entry called “TestStatusBarApp” appear in your status bar, and clicking it will show you three options - a working about window, a custom action to show off how you handle menu item events, and a quit command. Note that the app doesn’t appear in the dock or the cmd+tab window, as it resides solely in the status bar!

RubyMotion app in OS X status bar

How it works

As this is a RubyMotion template, it basically just creates the app skeleton for you, so we can delve into the code to understand better how it’s working, and how we can build on it.

To make the app status bar only, and have the dock and cmd+tab window ignore it as a targetable window, we have added the following to our Rakefile:

  app.info_plist['LSUIElement'] = true

We’ve also updated the spec/main_spec.rb to reference and test the status bar menu items instead of windows by default - you’ll want to update this as you make changes to your menus.

The rest of the good stuff happens inside app/app_delegate.rb as you might expect - and it’s surprisingly little code. In our applicationDidFinishLaunching method, we’re setting up a new NSMenu, and then creating a status item to sit in the system status bar:

  @status_menu = NSMenu.new

  @status_item = NSStatusBar.systemStatusBar.statusItemWithLength(NSVariableStatusItemLength).init
  @status_item.setMenu(@status_menu)

We’re using NSVariableStatusItemLength so that the status item can match the content. It’s easier to setup this way, but bear in mind that you probably don’t want the content that’ll appear in the status bar to be changing width too dramatically or too often, as that could be quite annoying!

Next up, we just set the highlight mode to true so that when you click the status bar item you get the blue highlight to show that you’ve clicked it, and we’re also setting the title to the app name:

  @status_item.setHighlightMode(true)
  @status_item.setTitle(@app_name)

After that we can setup some of our menu items, but we’re using a helper method to make things a little less verbose:

  def createMenuItem(name, action)
    NSMenuItem.alloc.initWithTitle(name, action: action, keyEquivalent: '')
  end

This just sets up a new menu item with the specified title and action (more on actions in a minute), and returns it. We can then use it like this:

  @status_menu.addItem createMenuItem("About #{@app_name}", 'orderFrontStandardAboutPanel:')
  @status_menu.addItem createMenuItem("Custom Action", 'pressAction')
  @status_menu.addItem createMenuItem("Quit", 'terminate:')

Actions are just references to methods that will act as event handlers for the menu item being clicked. The first and third items have actions that are “built-in” to OS X apps - namely with the default app structure, the about panel is already there and ready to use, and “orderFrontStandardAboutPanel:” shows that window. It contains a list of credits derived from a text file in the “resources” folder of your project (“Credits.rtf”). Within our new template, this is still the same as the one that comes with the default OS X template for RubyMotion, and should be edited to reflect your own details. The third item, with action “terminate:”, as you might expect refers to a method that is already accessible and that shuts the app down. It’s important to provide a way for the user to shutdown your status bar app, as it doesn’t appear in the dock or the “Force Quit” list!

The second menu item there is more interesting - “pressAction” is our own defined method, and acts as our event handler for a click on that item:

  def pressAction
    alert = NSAlert.alloc.init
    alert.setMessageText "Action triggered from status bar menu"
    alert.addButtonWithTitle "OK"
    alert.runModal
  end

This too is fairly basic - we’re just popping up an NSAlert dialog to show that the menu item has been clicked, and the event handler has correctly received the event. That’s all there is to creating a basic status bar app in RubyMotion!

Extending our app

Now let’s extend our default app skeleton provided by the gem to do something a little more interesting. We’ll have a menu item action that updates the content of the status bar item itself. First of all, let’s update our spec to expect a fourth menu item:

  it "has four menu items" do
    @app.delegate.status_menu.itemArray.length.should == 4
  end

Then, in our app delegate, lets setup a new menu item above the “quit” item, called increment:

  @status_menu.addItem createMenuItem("Increment", 'pressIncrement')

Lastly, let’s implement our event handler:

  def pressIncrement
    @count ||= 0
    @count += 1

    @status_item.setTitle("#{@count} times")
  end

Here we’re establishing a variable to keep track of our count, if one isn’t already set, then we’re incrementing it. Finally we’re referencing our main status item (which remember refers to the item that sits in the status bar itself, not any of the child menu items that show when you click on the status bar item), and we’re updating the title with the count. Now if you fire up the app, and click “Increment” from the status bar app menu, you’ll see the value in the status bar update!

RubyMotion app in OS X status bar extended with increment action

From here you can see how it’ll now be fairly straightforward to start building slightly more useful functionality into the app, either hooking into system stats or calls to provide data, or perhaps calling out to a third party API to do interesting things, making it all available from the status bar. In a future article we’ll look at how to run things in the background to do something interesting that updates the status bar without direct user interaction.

Feedback welcome

In the meantime, check out the gem, and let us know what you think! Any questions or comments on the above are welcome, either below or @KickCode on Twitter, and as always, subscribe to our mailing list or follow us to keep up to date with more tools and articles!




Available now in early access: Building Mac OS X apps with RubyMotion!


Unlock the power of Ruby in your Mac OS X apps to build everything from utility and productivity apps, to developer tools and helpers, to fully fledged desktop user interfaces. You'll integrate with web APIs, with core system functions, learn powerful ways to build user interfaces, and more. You'll learn how to best structure your apps and to take advantage of the Ruby syntax to make your development more efficient than building the app in Objective-C.


Learn more or purchase now.

blog comments powered by Disqus
Back to blog
 

Building Mac OS X apps with RubyMotion

Learn how to build Mac apps with using Ruby with this ebook, currently in early access, and with the finished version coming soon.


Purchase