How I Used Swift to Build a Menubar App for OSX

You know those menubar apps that sit next to our date/time in the top right corner of OSX?
images

Yeah, I think those are cool. Lets make one! :)

I should preface this tutorial by saying that I’ve only been learning Swift for about a week, I’m using Xcode beta4, and I’m sure there are far better ways of accomplishing this task. I simply wanted to share what I’ve learnt with other beginners (like myself).

Setting up the project

File -> New -> Project -> OSX -> Application -> Cocoa Application

Give it a cool name (I chose ‘menubar tut’ cuz I’m not THAT cool), and make sure you have selected Swift as the language to use.

Setting up our window

Click on MainMenu.xib, then click on the menu bar window…

images

… and you should get a view of our empty application.

Add a label

To add a label, we need to grab one from our objects folder, and drag it into the scene.

images

Resize to taste, and feel free to pick a cool font for it in the inspector if you are feeling creative.

Add a button

Do the same for a button, rename it to something amazing like “Press me!” images

Drag IBOutlets into AppDelegate

Next, we want to switch to assistant editor (the view that looks like a suit and bow tie in the upper right), and then “right click drag” our label and button into our AppDelegate.swift file.

images

For the label we just use a outlet.

images

For the button we actually want to create both an outlet and an action. images images

TL:DR Code

Note: If you are just planning on copy/pasting and then splitting…. make sure you read the visibility section below. You have to manually mess with your info.plist file for it to fully work.

Here is the full code. I will proceed to break down each part aftwards.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import Cocoa

class AppDelegate: NSObject, NSApplicationDelegate {
                            
    @IBOutlet weak var window: NSWindow!
    @IBOutlet weak var theLabel: NSTextField!
    @IBOutlet weak var theButton: NSButton!
    
    var buttonPresses = 0;
    
    var statusBar = NSStatusBar.systemStatusBar()
    var statusBarItem : NSStatusItem = NSStatusItem()
    var menu: NSMenu = NSMenu()
    var menuItem : NSMenuItem = NSMenuItem()
    
    override func awakeFromNib() {
        theLabel.stringValue = "You've pressed the button \n \(buttonPresses) times!"
        
        //Add statusBarItem
        statusBarItem = statusBar.statusItemWithLength(-1)
        statusBarItem.menu = menu
        statusBarItem.title = "Presses"
        
        //Add menuItem to menu
        menuItem.title = "Clicked"
        menuItem.action = Selector("setWindowVisible:")
        menuItem.keyEquivalent = ""
        menu.addItem(menuItem)
    }
    
    func applicationDidFinishLaunching(aNotification: NSNotification?) {
        self.window!.orderOut(self)
    }
    
    @IBAction func buttonPressed(sender: NSButton) {
        buttonPresses+=1
        theLabel.stringValue = "You've pressed the button \n \(buttonPresses) times!"
        menuItem.title = "Clicked \(buttonPresses)"
        statusBarItem.title = "Presses \(buttonPresses)"
    }
    
    func setWindowVisible(sender: AnyObject){
        self.window!.orderFront(self)
    }
}

StatusBar’s, StatusItem’s, Menu’s, MenuItem’s

The hardest part for me was wrapping my brain around the nested qualities of StatusBar’s and Menu’s.

Basically, StatusBar’s (the things in the Menubar) have StatusItems (things you can click), and Menu’s have MenuItem’s. We need all these things to accomplish a little pulldown menu.

How do you put these together? Well, StatusItems CAN be a NSMenu.

So, before all my function declarations, I initialze everything we need upfront.

1
2
3
4
var statusBar = NSStatusBar.systemStatusBar()
var statusBarItem : NSStatusItem = NSStatusItem()
var menu: NSMenu = NSMenu()
var menuItem : NSMenuItem = NSMenuItem()

Note: Initializing the statusBarItem is pointless and would actually be better to make an optional. You will soon see why. For the sake of not delving too far into optionals, I leave that up to you as an exercise.

awakeFromNib

This is our entry point for the entire application. Technically, it is actually called after our interface/window has been loaded. This is where we handle some of the initializations needed.

1
2
3
4
5
6
7
override func awakeFromNib() {
    theLabel.stringValue = "You've pressed the button \n \(buttonPresses) times!"

    //Add statusBarItem
    statusBarItem = statusBar.statusItemWithLength(-1)
    statusBarItem.menu = menu
    statusBarItem.title = "Presses"

After setting the initial value of the label, we are going to load the statusBarItem into the statusBar.

We are actually asking our statusBar object to create a statusItem for us. This is why we did not need to initialize this variable in the section above. Normally, this method accepts a different value than -1, but there is currently a bug in Xcode that doesn’t let us do that. You can read more about it over here:
http://stackoverflow.com/questions/24024723/swift-using-nsstatusbar-statusitemwithlength-and-nsvariablestatusitemlength

Next, we give the statusBarItem our menu that we are building, and give it a title. You can give NSStatusItems other things such as images and tooltips. I encourage experimentation on your own :)

1
2
3
4
5
//Add menuItem to menu
menuItem.title = "Clicked"
menuItem.action = Selector("setWindowVisible:")
menuItem.keyEquivalent = ""
menu.addItem(menuItem)

Finally, we add the menuItem to our menu. MenuItem’s need a minimum of 3 basic properties to work. Title is self explanatory. Action is what function will get called when we click it, and keyEquivalent is its shortcut key when the menu is open. Having no value for the later gives it no shortcut key.

More on the menuItem.action later in the tutorial.

buttonPressed

What we want to do is increment a counter when the button is pressed.

Sounds crazy right? I decided to do this for the sake of the tutorial to show concurrency between our OSX window and the status bar. I encourage you to do something more interesting than what I did ;)

1
2
3
4
5
6
@IBAction func buttonPressed(sender: NSButton) {
    buttonPresses+=1
    theLabel.stringValue = "You've pressed the button \n \(buttonPresses) times!"
    menuItem.title = "Clicked \(buttonPresses)"
    statusBarItem.title = "Presses \(buttonPresses)"
}

We are incrementing our buttonPresses variable that we defined at the top of our file and updating strings in various locations. If you are unfamiliar with the += operator, its shorthand for saying:
buttonPresses = buttonPresses + 1

setWindowVisible

1
2
3
func setWindowVisible(sender: AnyObject){
    self.window!.orderFront(self)
}

When this function is called, it will not only set the window to be visible, but also bring it to the front.

Something you may want to experiment with is having a function that flips the visibility of the window by utilizing:
self.window?.visible == true
and
self.window!.orderOut(self)

Dock Visibility

If you run the program it would work fine, however there would be evidence of our app running in the dock.

images

Open the info.plist file that should be in our applications “Supporting Files” folder.
Right-Click -> Add Row
Finally for the left side select “Application is an agent (UIElement)” and set the value to true on the right side. It should look like this:
images

One more thing….

1
2
3
func applicationDidFinishLaunching(aNotification: NSNotification?) {
    self.window!.orderOut(self)
}

Originally, I had this line of code in our awakeFromNib function, however it never made the window disappear. After I moved the line of code here, it magically worked.

I’d love if someone could enlighten me about why the window was not loaded yet in the awakeFromNib function.

Conclusion

Well, that was REALLY cool! We have a working menubar app with an interactive window associated with it.

I believe its helpful to think of what sort of things you ALWAYS want to be aware of, and try writing an app for that. An example, it would be cool if I knew how much time I spent blogging or if certain people come online in forum’s I use.

Something that I did not address was always launching this app on startup. I’ll leave that one up to you guys!

I hope you enjoyed! Let me know if you have any ideas for things that I can write tutorials about. In the meantime, enjoy learning Swift like I am!

Comments