Recently I was writing a post about NSButton styles on my blog, and when I got to the part about toolbar buttons (Textured Rounded), I realized that I actually had no idea how to create a toolbar in AppKit, so I had to do some research first. I found some help in this sample project from Apple and this project on GitHub, and then I did some experiments with different combinations of things to see what works where.
This could probably be a whole separate blog post on its own (and maybe I'll make this into one), but for now I've put it here to not make that main article even longer.
Here's what I've found:
There are a few different kinds of buttons you can put in the toolbar:
- A large, plain, unbordered icon - this was an old toolbar style that started disappearing from macOS long ago (replaced by real buttons) and was practically gone by the time of macOS Sierra, and only survived in preferences windows
- A bordered toolbar item - option added in Catalina that automatically creates a textured rounded button for you
- A toolbar item with a custom view which is a textured rounded button
- A toolbar item with a custom view which is a popup, search field, segmented control etc.
There are also two ways you can build a toolbar with all of these:
- On the storyboard
- In code
To create a toolbar on the storyboard, drag a toolbar from the Object Library into your window - it will automatically snap to the top of it. Now, double click the toolbar to show a sheet with "Allowed Toolbar Items" and "Default Toolbar Items" - you only edit the toolbar elements inside that sheet.
You can throw out the few standard items added there automatically, and then drag items from the Library into the top area (Allowed Items) - either an "Image Toolbar Item" (this will create an NSToolbarItem
with an image), or a specific control like the Textured Rounded Button or a Segmented Control (this will create a toolbar item with a custom view). Then configure the toolbar item and/or the control inside, and also drag it into the bottom bar (Default Items) if it should appear by default.
To build a toolbar in code, make your class implement the NSToolbarDelegate
protocol and assign it as the toolbar's delegate. You will need to implement 3 delegate methods:
toolbarAllowedItemIdentifiers(_:)
- this returns a list of string-based identifiers for all available items (just make up any unique name for each item)
toolbarDefaultItemIdentifiers(_:)
- returns a subset of the above list that is displayed by default before the user customizes the toolbar
toolbar(_:, itemForItemIdentifier:, willBeInsertedIntoToolbar:)
- this is where you create and return an
NSToolbarItem
for the passed identifier
- set the item's labels, target/action and either an
image
&isBordered
or a custom view like anNSButton
that you create in code set in theview
property
Now, the question is: since you have a few different ways to build a toolbar, plus also in any case you can set the sizing in various ways, how exactly should you do it to make it look like in system apps?
There are a few things here to keep in mind, based on the docs and what I've found:
- the bordered toolbar item option was added in Catalina, but it only works there when set in code - the storyboard option only works in Big Sur+
- bordered toolbar item buttons and explicitly built buttons look slightly different - the label below highlights on click for bordered toolbar items, but doesn't highlight for custom buttons
- you can explicitly set a size for the item and/or button, but if you keep it at auto it displays as you'd expect on Mojave and above (but not in older versions, where in most cases it shows up weird, either too small or too large)
I can't really tell if Apple's apps build toolbars in code and how they use NSToolbarItem
, since it's not a view object, but I could at least check how the result looks visually - and the answer is… each app does it differently :D
On Catalina, a few apps like Notes, Finder, Photos have buttons with ~39pt size, which is what I get if I leave the size at auto or set it to 40. Mail has 40pt buttons, Photos has 41pt buttons and Safari has 38pt buttons. They also differ in how the labels work on click - in Mail they highlight, in Finder they don't. The rest doesn't have an option to show labels.
On Big Sur, each icon/button generally has a different size depending on the size of the specific symbol used (the padding around the symbol tends to stay the same). Although in Mail all buttons are of the same, slightly larger size (they're actually one-segment segmented controls).
So I can only say what I think is the recommended way, based on two things:
- that the
bordered
property was added fairly recently, which means Apple probably wants you to use it - that the
minSize
/maxSize
properties were deprecated in Big Sur, which means Apple probably doesn't want you to use them
So here's my recommendation for how you should do it, depending on which macOSes you want to support:
- build the toolbar on the storyboard or in code, how you prefer
- for single buttons use bordered image toolbar items ("Image Toolbar Item" with bordered = on or
NSToolbarItem
withimage
andbordered
set) - use SF Symbols for icons
- if you want to use some advanced symbols from Monterey, build the toolbar in code and have
if #available
code paths - do not set
minSize
/maxSize
- build the toolbar in code
- for single buttons use bordered image toolbar items (
NSToolbarItem
withimage
andbordered
set) - have
if #available
code paths to set a classic image icon for Catalina and SF Symbols icon(s) for Big Sur & Monterey - do not set
minSize
/maxSize
- build the toolbar in code
- have separate
if #available
code paths:- for Big Sur & Monterey, use bordered image toolbar items with SF Symbols (auto size)
- for Catalina, use bordered image toolbar items with an image you provide (auto size)
- for Mojave, use toolbar items with
view
set to a manually builtNSButton
with a.texturedRounded
bezel (button'sbordered
=true
), also auto size
- same as above, but in the custom
NSButton
code path for Mojave and earlier also set bothminSize
andmaxSize
on the item to 40 x 23 (height doesn't matter as long as it's not too large)