I’m pretty new to MacOS development, I’m coming from a past mixed between web dev and iOS (My latest app: StockPapers).
Today I want to share with you what I’ve learned day by day in this week trying to create a clone of my nodeJS cli, splash-cli.
We are going to build a status bar clock application with “reminders”, where the user will be able to schedule notifications.
We’ll see how to prevent to open the same window multiple times, notifications and so more!
What do you need?
- Basic knowledge of the Swift language (or maybe not)
- A machine running Xcode
- Xcode 10.2 or newer (even a lower version is ok, but this will may change some methods in the code, depending on your swift version)
📌 Create a new Project
First of all, we need to create a new Xcode Project, so go under
File > New > Project... or press
Select MacOS as platform and choose the first, option: Cococa App.
Then choose a name for the app, in this example, I’ve chosen “Advanced Clock” and make sure to check the “Use Storyboards” option.
Usually, on iOS, I’m not used to using storyboards but for
MacOS I find them really helpful.
🔧️ Project Setup
We need to setup our project before hacking the code: firstly we need to delete the default
WindowController and the relative
ViewController because we don’t need to open a window when the application starts. Then you should have something like in the screenshot below with just the
PS: Don’t delete the
Main Menu, the action is not reversible and if you ever need it you need to rebuild it by yourself and it’s very frustrating…
Okay so now we need to setup our clock app, almost all our code will be placed in the
We can easily delete the
applicationWillTerminate method, right now we don’t need it.
Then we can create our
NSStatusItem this will be our item in the status bar, more specifically will be the container for our button.
To create our status bar item just write the following:
We have assigned a placeholder text for the moment, now it’s time to schedule a timer that updates our title with the current time every second.
To do that we declare another class variable named
timer with optional type
Timer? and make it
nil by default.
Then in the
applicationDidFinishLaunching under our
title assignment, we can create our Timer like the following
I’ve also created an objective-c function that is called every time the Timer gets fired (every second).
Below some utility functions to make everything more readable and clean, one is for the
Date class and the other one is for
Ok now you can run your app and if you’ve done everything right, you should have your clock in the status bar.
Congratulations! 🎉 You’ve built your first MacOS Application! But wait! We have a bug! Maybe you haven’t noticed yet but if the
statusBarItem is highlighted it will not update until we move our focus out of it.
To fix that we need to edit our
Timer and add a row to start it like the following:
Awesome! You’ve fixed your first bug in the app! Now let’s make it a little bit more interesting 💪
✏️ Add a Menu
Okay now it’s time to make our application more user-friendly, let’s add some features and preferences.
First of all let’s write some code to handle our Preferences, to do that I usually create a
struct that handles
UserDefaults for small settings like booleans numbers, strings, enums, etc.
Now we can move with the menu stuff, declare a new menu variable in the AppDelegate and create a menu with
NSMenu() then add some items, like:
- Flashing time separators
- Show/Hide Seconds
- Show/Hide Dock Icon (we’ll se how later)
- Quit the application (quite useful)
Before we go through the code I want to share with you other 2 little extensions that will help to archive good code readability and functionality
So.. now I’ve refactored a bit the
AppDelegate to make it more clear and concise. That’s how it looks with the menu implementation:
This is how the application is looking right now:
By the way, now it’s missing some implementations.
showDockIcon items are useless at the moment. Instead of these functions the
showSeconds setting it’s handled in the
NSMenuItem action we update the item it self with the new preferences, since these are checkboxes we update the state, the
Bool+Extension.swift its a statement that returns
.off respectively when a Boolean is
false. This saves us from writing
Preferences.showDockIcon ? .on : off instead of
️️⚙️ ️Preferences: The Dock Icon
Okay, here we are, we have a status bar application but we also have a dock icon, and as a status bar application, we don’t want the dock icon to be visible.
Look at the top right corner of your monitor, do you see them?
Imagine if every app in your status bar would have an icon in your dock… That’s not great user experience.
We can handle the Dock Icon via
NSApplication.ActivationPolicy. To do that I wrote a little helper, a struct with 2 methods to set/read the A
We can now edit the
toggleDockIcon method to handle this preference, let’s see how implement this helper.
Now copy and paste the method you’ve just written (line 7 of the gist above) at the end of the
⚙️ Preferences: Flashing Separators
Now it’s the time of “flashing separators”, how can we make a better clock if the standard one has more functionalities? So let’s implement this simple feature that even Apple’s default clock has.
How? Easier than you think, we need a variable that handles the status of the separators (visible/hidden, or maybe better 0/1, or we can even recycle
NSControl.StateValue) and if it’s 0 do a string replacement in with “:” -> “ “ in our title (remember?).
A better way to do that is checking whenever the seconds are even (or odd) and use this instead of the variable (this is ok only if you want to flash dots 1 time per second)
Below how it should look like with flashing separators:
Wait! We’re missing the core experience of the whole application! Right now why should someone download an alternative clock with no extra features?
So let’s add the reminders feature, it’s pretty simple, and you’ll learn how to create and present a window.
We can start creating a new
struct type and call it
Then create a new variable of type
[Reminder] and add empty array as default value.
var reminders: [Reminder] = 
Now we can write some logic stuff:
- Add a MenuItem to the StatusMenu
- Add a submenu with all the reminders
- Add a MenuItem that opens a new window to schedule a new Reminder.
- Delegate reminder and the view controller in the
Creating the NSMenuItem and its submenu
As we have already done before for the other elements, let’s create a new
NSMenuItem and this one like the
statusBarItem will have a menu property, this will be populated with all the reminders inside our
reminders variable declared before.
How can I add a new Reminder?
Now we need to create an interface to add reminders, to do that open your
Main.storyboard and create a view controller with an
NSTextField for the title,
NSTextView for the description and
NSDatePicker for the date.
You can do this also from code, programmatically, but since this article is becoming really long I’ll show you the fastest way to archive a basic window with controls.
Ok now let’s create a new file, this will be our view controller, in case you haven’t delete it yet, you should have a file named ViewController, you can edit this one instead of create a new file. Rename it to NewReminderVC and rename also the class inside, you should have something like this one below:
To save time I’ve just added a new Delegate Protocol on top of the file, this will be used to handle the submit event.
Okay, you’re all set, go in your storyboard, select the view controller you’ve just created and assign this new class to it:
Make sure to add a Storyboard ID, for practicality I name it just like the view controller class.
We’ll use the Storyboard ID to identify our view later in the code.
Now with the view controller selected, click on the Assistant Editor button on the top right corner of Xcode.
This will split your screen in 2 editors.
Make sure to have the right file selected (the right file is the file of the view controller), then drag and drop (with ctrl pressed) your items in the class.
You should have 3 IBOutlets and 1 IBAction, the action is connected with the button, the other 3 outlets are the title, the description, and the date picker.
Ok, now you have a view but how can we present it? Easy, create a new file with this helper, this will give you a function named
getVC you need to pass the Storyboard ID and the view controller class
Ok now let’s add the missing function in the
AppDelegate. The first thing to do is declaring a new constant on top of the file:
let REMINDERS_WINDOW_CONTROLLER: NSWindowController = NSWindowController(window: nil)
All right, now we miss an action, let’s add some stuff:
Firstly we need to edit our
reminders variable and add a
I’ve created a utility function to easily create the reminders menu, now let’s create the menu item, and don’t forget to add it to the menu.
addReminder it’s still not declared so Xcode will alert you with an error, let’s fix that by declaring it:
floating level read below:
See Window Levels for a list of possible values. Each level in the list groups windows within it in front of those in all preceding groups. Floating windows, for example, appear in front of all normal-level windows. When a window enters a new level, it’s ordered in front of all its peers in that level.
Last but not least, we need to implement delegates and do something inside the NewReminderVC
viewDidLoad method we set our default values, then
onSubmit we create our new reminder and then we call the
onSubmit method of the delegate.
Finally, we need to conform with the delegation from NewReminderVC, to do that add an extension of
AppDelegate like the following:
Looking for SwiftUI version? Make sure to follow the Apple tutorial on creating MacOS apps with SwiftUI.
You can find the entire project on my GitHub at Advanced Clock.
Now you should be able to run your application. These are the basics for Mac Development, or at least what I’ve learned in the past weeks by searching on Google and StackOverflow. Below I leave you some articles here on Medium about some fundamentals concepts