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?
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…
… 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.
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!”
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.
For the label we just use a outlet.
For the button we actually want to create both an outlet and an action.
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.
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.
var statusBar = NSStatusBar.systemStatusBar()
var statusBarItem : NSStatusItem = NSStatusItem()
var menu: NSMenu = NSMenu()
var menuItem : NSMenuItem = NSMenuItem()
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.
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"
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 :)
//Add menuItem to menu
menuItem.title = "Clicked"
menuItem.action = Selector("setWindowVisible:")
menuItem.keyEquivalent = ""
menu.addItem(menuItem)
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 ;)
@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)"
}
buttonPresses = buttonPresses + 1
setWindowVisible
func setWindowVisible(sender: AnyObject){
self.window!.orderFront(self)
}
Something you may want to experiment with is having a function that flips the visibility of the window by utilizing:
self.window?.visible == true
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.
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:
One more thing….
func applicationDidFinishLaunching(aNotification: NSNotification?) {
self.window!.orderOut(self)
}
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!