macOS Preferences Window with Storyboards

Recently I started with a fresh project from scratch in which I wanted to implement a preferences window with tool bar tabs as they are common on macOS. It is surprisingly easy.

This is one of those things which are very simple but still have to be found out somehow. Like the connection of controls with action methods through control-dragging in interface builder. When I started with Xcode in 2010 I was desperate and close to giving up after a whole day of trying to make those things work together somehow. Until I stumbled over the solution in some YouTube tutorial buried under tons of irrelevant search results. Probably I would have found that information in some costly and thick book, but I am not that learning type. It was so intuitive that I could not imagine it beforehand. Just like “uninstalling” apps on macOS. Anyway, here is how to set up a preferences window quickly and easily in interface builder.

Example App

Just to give away before I start with the steps: I created an example app project which is available on GitHub.

Creating the App

I guess you probably have a project created but for the sake of completeness I mention this explicitly as it was the first step in the creation of the example project. It is a plain macOS app with Swift and storyboards, nothing else.

Delete the Default View Controller

The storyboard in the project template contains a view controller. Though it is of the wrong type and not much use for this case. Select and delete it.

Screenshot of the storyboard in Xcode

Add a Tab View Controller

Add a new tab view controller by opening the Xcode library. There is a toolbar button for opening the library. I use the keyboard shortcut since I can remember. Search for the tab view controller and drag it into the empty space of your storyboard.

Screenshot of the Xcode library

Control-drag from the window controller to the tab view controller in the left sidebar which shows the view hierarchy in the storyboard. Then a popup appears asking for the relationship segue with the only option being “window content”. Select that to define the tab view controller as the content view controller of the window. A segue must appear from the window to the tab view controller as it did in case of the default view controller you deleted previously.

Change the Appearance

Next comes the convenient “magic” which turns the tab view into the selectable toolbar we know and want. Select the tab view controller and open the attributes inspector of Xcode in the right sidebar. The “Style” property must be changed from whatever it is (likely “Tabs on Top”) too “Toolbar”. Tada! Now you can continue with adapting the appearance of the individual tabs. Select each in the view hierarchy to set their label and image. The simplest choice are system images defined by their name.

Screenshot of the storyboard in Xcode

Now you can add some labels or other dummy controls in the child view controllers of the tab view controller to check its expected functionality. The end result is looking good already and achieved without a single line of (typed) code.

Screenshot of the end product

Why Storyboards and not SwiftUI?

Just to explain why I am writing about an implementation with storyboards and not SwiftUI in 2022: I am familiar with the concepts of SwiftUI from my previous work with web technologies which offer the counterpart in a browser. I was completely on the hype when SwiftUI was introduced in 2019. But my excitement for SwiftUI was muffled quickly. With my focus on macOS I hit limitations and shortcomings of SwiftUI too often, even when just starting small and experimental projects. And the editing experience still is slightly annoying and still has too many hiccups in Xcode. Since last year I would finally consider SwiftUI an actual option for some kinds of projects, though.

I always loved storyboards and SwiftUI reassured me they still are great, too. I know the drawbacks and daily pain they can cause, especially in version control. And I am still used to occasional Xcode crashes when I am working in interface builder. With Xcode 13. In 2022.

In the project I started to work on I have a lot to take into consideration at once and in the beginning. I did not want to add another potential field of problems by introducing experiments with a new user interface framework. Especially, if some nasty problems are obvious before even starting based on my experience with SwiftUI so far. The project architecture luckily allows later partial replacement of storyboard user interface with SwiftUI. For now the battle tested storyboards suffice. In that project (a file provider) the user interface is a secondary anyway. Once the basic user interface is implemented, it will not be changed much or often. Once a user completed the app configuration the user interface of the app will appear less often on the screen than the app’s service is actually used.

About The Author

Peter Thomas Horn is a professional software developer at Open-Xchange specialized on the Apple platform. He previously worked a decade across the full stack of various web technologies. Originally started with Java on Windows at the age of 12 years. While staying humble in throwing around buzzwords like "VR" and "machine learning" he occasionally experiences problems and the fitting solutions considered worth sharing.