How to Create AppKit Palette Menus in Objective-C

Apple introduced palette menus in the WWDC 2023 session What’s new in AppKit. Despite being introduced three years ago, this API isn’t mentioned often. The relevant API lives in NSMenu in the NSPaletteMenus category. This brief tutorial demonstrates how to use palette menus in your macOS apps using modern Objective-C. The code samples in this article assume your app targets macOS 14.0 or later.

What is a Palette Menu?

A palette menu is an NSMenu whose items are presented horizontally instead of vertically, making it useful for compact groups of related choices: colors, tags, flags, drawing tools, annotation filters, ratings, priorities, and other visual options.

The Palette Menu Objective-C API

@interface NSMenu (NSPaletteMenus)
 
// Creates a palette menu displaying user-selectable color 
// tags using the provided array of colors and optional titles.
+ (instancetype)paletteMenuWithColors:(NSArray<NSColor *> *)colors 
                               titles:(NSArray<NSString *> *)itemTitles 
                     selectionHandler:(nullable void (^)(NSMenu *menu))onSelectionChange API_AVAILABLE(macos(14.0)) NS_REFINED_FOR_SWIFT;
 
// Creates a palette menu displaying user-selectable color tags
// using the provided template image, tinted using the specified
// array of colors.
+ (instancetype)paletteMenuWithColors:(NSArray<NSColor *> *)colors 
                               titles:(NSArray<NSString *> *)itemTitles 
                        templateImage:(NSImage *)image 
                     selectionHandler:(nullable void (^)(NSMenu *menu))onSelectionChange API_AVAILABLE(macos(14.0)) NS_REFINED_FOR_SWIFT; 
 
// The presentation style of the menu. 
@property NSMenuPresentationStyle presentationStyle API_AVAILABLE(macos(14.0));
 
// The selection mode of the menu.
@property NSMenuSelectionMode selectionMode API_AVAILABLE(macos(14.0));
 
// The menu items that are selected. 
@property (copy) NSArray<NSMenuItem *> *selectedItems API_AVAILABLE(macos(14.0));
 
@end

Unfortunately designing a palette menu in Interface Builder is not supported. There are two main ways to create a palette menu:

1) Use AppKit’s color-palette convenience constructors.
2) Create a normal NSMenu and set its presentationStyle to NSMenuPresentationStylePalette.

Example 1: A Simple Color Tag Palette

Here is a compact color picker for assigning a tag color:

// Helper method that builds the color tag picker.
- (NSMenuItem *)tagColorMenuItem
{
    NSArray<NSColor *> *colors = @[NSColor.systemRedColor,
		                   NSColor.systemOrangeColor,
		                   NSColor.systemYellowColor,
		                   NSColor.systemGreenColor,
		                   NSColor.systemBlueColor,
		                   NSColor.systemPurpleColor,
		                   NSColor.systemGrayColor];
 
     NSArray<NSString *> *titles = @[@"Red",
	                             @"Orange",
	                             @"Yellow",
	                             @"Green",
	                             @"Blue",
	                             @"Purple",
	                             @"Gray"];
 
 
     NSMenu *paletteMenu = [NSMenu paletteMenuWithColors:colors
		                                  titles:titles
		                        selectionHandler:^(NSMenu *menu) 
     {		
         NSMenuItem *selectedItem = menu.selectedItems.firstObject;
	 NSInteger selectedIndex = (selectedItem) ? [menu indexOfItem:selectedItem] : NSNotFound;
	 if (selectedIndex != NSNotFound)
         {
	    NSColor *selectedColor = colors[selectedIndex];
	    NSLog(@"Selected color: %@",selectedColor);
	    // TODO: Apply the selected color here.
	 }
      }];
 
      paletteMenu.selectionMode = NSMenuSelectionModeSelectOne;
 
      // Wrap the paletteMenu in an NSMenuItem.	
      NSMenuItem *parentItem = [[NSMenuItem alloc] initWithTitle:@"Tag Color" action:nil keyEquivalent:@""];
      parentItem.submenu = paletteMenu;
 
      return parentItem;
}

To display the palette menu we simply add the item returned from the -tagColorMenuItem method to an NSMenu and present it like any other menu. The code below demonstrates how to do this:

// -showPaletteMenu: here is the action method of an NSButton.
-(void)showPaletteMenu:(NSButton*)sender
{
    NSMenu *menu = [[NSMenu alloc] initWithTitle:@"Palette Menus"];
    [menu addItemWithTitle:@"Palette Menus" action:nil keyEquivalent:@""];
    [menu addItem:[self tagColorMenuItem]]; // <-- This line adds the palette menu.
 
    NSPoint location = NSMakePoint(NSMidX(sender.bounds) - (menu.size.width / 2.0),
		                   NSMinY(sender.bounds) + (menu.size.height));
 
    [menu popUpMenuPositioningItem:menu.itemArray.lastObject 
	                atLocation:location 
		            inView:sender];
}

The code above gives us the following result:

Tag selector palette menu in AppKit.

The titles are not displayed in the palette, but they are still important for accessibility features such as VoiceOver.

Example 2: A Palette with a Template Image

The second convenience constructor (+paletteMenuWithColors:titles:templateImage: selectionHandler:) lets you provide a template image. This is useful when a colored symbol communicates the choice better than a plain color dot. For example we can create a flag color picker with the following code:

- (NSMenuItem *)flagColorMenuItem
{
     NSArray<NSColor *> *colors = @[NSColor.systemRedColor,
		                    NSColor.systemOrangeColor,
		                    NSColor.systemYellowColor,
		                    NSColor.systemGreenColor,
		                    NSColor.systemBlueColor];
 
     NSArray<NSString *> *titles = @[@"Red Flag",
		                     @"Orange Flag",
		                     @"Yellow Flag",
		                     @"Green Flag",
		                     @"Blue Flag"];
 
     NSImage *flagImage = [NSImage imageWithSystemSymbolName:@"flag.fill" accessibilityDescription:@"Flag"];
 
      __weak PMViewController *weakSelf = self;
      NSMenu *paletteMenu = [NSMenu paletteMenuWithColors:colors
		                                   titles:titles
		                            templateImage:flagImage
		                         selectionHandler:^(NSMenu *menu) 
       {
	    __strong PMViewController *strongSelf = weakSelf;
	    if (strongSelf == nil) { return; }
 
            NSMenuItem *selectedItem = menu.selectedItems.firstObject;
	    if (selectedItem == nil) { return; }
 
	    NSUInteger index = [menu.itemArray indexOfObject:selectedItem];
	    if (index == NSNotFound) { return; } // This would be unexpected.
 
	    [strongSelf applyFlagColor:colors[index] title:titles[index]];
      }];
 
     paletteMenu.selectionMode = NSMenuSelectionModeSelectOne;
 
     NSMenuItem *parentItem = [[NSMenuItem alloc] initWithTitle:@"Flag Color"
	                                                 action:nil
		                                  keyEquivalent:@""];
     parentItem.submenu = paletteMenu;
 
     return parentItem;
}
 
- (void)applyFlagColor:(NSColor *)color title:(NSString *)title
{
    NSLog(@"Selected flag color %@ : %@",color, title);
    // TODO: Apply the color.
}
Flag selector palette menu in AppKit.

Bonus Tip: In this sample a stub method for -applyFlagColor:title: is shown. Note that the selection handler does not capture self directly to call the –applyFlagColor:title: method. This matters if the view controller keeps a strong reference to the menu, for example if the menu is to be reused in subsequent presentations. The menu owns its selection handler block, and blocks strongly capture Objective-C objects by default. If the block captured self, the ownership graph could become view controller -> menu -> block -> view controller, creating a retain cycle.

To avoid a retain cycle, assign self to a weak variable before creating the block, then promote it to a local strong variable inside the block. The local strong reference keeps the controller alive for the duration of the handler after it has been retrieved. The weak/strong pattern makes the lifetime behavior explicit and avoids the object disappearing halfway through the handler.

Example 3: A Manually Created Tool Picker Palette Menu

The palette menu class constructors are convenient, but they are not the only way to create a palette. To manually create a palette menu, instantiate an NSMenu instance and set its presentationStyle to NSMenuPresentationStylePalette.

menu.presentationStyle = NSMenuPresentationStylePalette;

Below is an example of how you can create a tool picker palette menu:

- (NSMenuItem *)toolPaletteMenuItem
{
     NSMenu *toolMenu = [[NSMenu alloc] initWithTitle:@"Tool"];
     toolMenu.presentationStyle = NSMenuPresentationStylePalette;
     toolMenu.selectionMode = NSMenuSelectionModeSelectOne;
 
     NSArray<NSDictionary<NSString *, NSString *> *> *tools = @[
		@{ @"title": @"Select", @"symbol": @"cursorarrow" },
		@{ @"title": @"Pen",    @"symbol": @"pencil" },
		@{ @"title": @"Text",   @"symbol": @"textformat" },
		@{ @"title": @"Shape",  @"symbol": @"square.on.circle" },
		@{ @"title": @"Erase",  @"symbol": @"eraser" }
     ];
 
     NSMenuItem *initialSelectedItem = nil;
 
    for (NSDictionary<NSString *, NSString *> *tool in tools) {
	NSString *title = tool[@"title"];
	NSString *symbolName = tool[@"symbol"];
 
	NSImage *image = [NSImage imageWithSystemSymbolName:symbolName
                         	   accessibilityDescription:title];
	image.template = YES;
 
	// Give all menu items in the toolMenu the same target action.
 	NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:title
			                              action:@selector(selectToolFromMenuItem:)
			                       keyEquivalent:@""];
	item.target = self;
	item.image = image;
	item.representedObject = title;
 
	[toolMenu addItem:item];
 
	if ([self.currentToolName isEqualToString:title])
	{
	   initialSelectedItem = item;
	}
     }
 
     if (initialSelectedItem != nil)
     {
	 toolMenu.selectedItems = @[ initialSelectedItem ];
     }
 
     NSMenuItem *parentItem = [[NSMenuItem alloc] initWithTitle:@"Tool"
                                                   	 action:nil
		                                  keyEquivalent:@""];
     parentItem.submenu = toolMenu;
 
     return parentItem;
}
 
- (void)selectToolFromMenuItem:(NSMenuItem *)sender
{
     NSString *toolName = sender.representedObject;
     self.currentToolName = toolName;
     NSLog(@"Selected tool: %@", toolName);
}
A tool picker created using a palette menu in AppKit.

In this example, a __weak self variable is not required because selection is handled with the standard target/action pattern rather than a block. The menu item invokes selectToolFromMenuItem: on its target (a weak reference), and there is no selection-handler block capturing the view controller.

Where Palette Menus Fall Short

Palette menus are useful, but they are not always the best choice. They work best when the user is choosing from a small, fixed set of options. Once the interaction becomes more complex, you may have to embed a custom view inside a NSMenuItem or choose a different AppKit control.

Palette menus also do not provide room for explanation. A horizontal row of icons or colors is compact, but that compactness comes at the cost of context. If each option needs a description, preview, secondary value, keyboard shortcut explanation, or multiple lines of text, the palette layout cannot be used.

Another thing to watch for is item count. Palette menus are best for small groups. Five colors, four annotation modes, or three priority levels work well. As the number of items grows, the palette becomes harder to scan, harder to fit, and less menu-like. If you have a large collection of options, use a standard menu or build your own dedicated picker.

The color convenience constructors are intentionally specialized. They work well when your menu items can be described by a simple list of colors and titles, with an optional shared template image. If each item needs its own image, represented object, validation behavior, or command handling, skip the convenience constructors and build the menu items manually, as shown in Example 3.

Finally, be careful not to use palette menus just because they look modern. A vertical menu is often better for destructive actions, rarely used choices, or options that need keyboard discoverability. Palette menus shine when the choices are visual, compact, and frequently used. If the user has to stop and decode what each item means, the palette is probably the wrong interface.

Easily Bind an NSProgress Object to an NSProgressIndicator in Objective-C [Open Source]

In UIKit UIProgressView has an observedProgress property. If you set the observedProgress property on a UIProgressView, it will automatically update its appearance when you make changes to the NSProgress object. On macOS (in AppKit at least) NSProgressIndicator does not have an equivalent API. I created a simple category on NSProgressIndicator that adds an observedProgress property on NSProgressIndicator. The source code is available on Github here.

Adding Force Touch Features to macOS Apps with a Custom Gesture Recognizer [Open Source]

I created a simple subclass of NSGestureRecognizer, ATForceTouchGesture, to simplify the process of adding Force Touch features to macOS apps. You can use this gesture recognizer to add a feature like force clicking to start editing a label (see the screenshot below).

Image captures 'force click' to edit using ATForceTouchGesture.

There is a sample project available on Github here.

Exporting NSTableView to HTML [Open Source]

I needed to export the contents of a NSTableView to HTML, so I wrote a little NSTableView subclass in Objective-C to do this.

ATHyperTextTableView is a simple NSTableView subclass that makes exporting a table view to HTML easy. You can customize the look of the exported HTML table with your own CSS too.

Screenshots Below:
Screenshot of NSTableView.
Screenshot of exported HTML from a the tableView, loaded into a WebView.
Screenshot of the exported HTML loaded into a WebView.

Screenshot of exported HTML from a the tableView, loaded into a WebView with custom CSS set.
Screenshot of the exported HTML loaded into a WebView, styled with custom CSS.

There is a sample project available on Github here.

How to Disable NSScrollView Scrolling

Unlike UIScrollView on iOS, NSScrollView on Mac does not have a handy scrollEnabled property you can use to temporarily disable scrolling.

If you Google around, you can find several posts on websites like stackoverflow that ask questions like How can I disable the vertical scrolling of a NSScrollView? Some have suggested setting the hasVerticalScroller property to NO as the answer. However, the hasVerticalScroller property only effects the visibility of the scroller, a value of NO does not actually prevent scrolling.

There are also ways to constrain scrolling from an NSView subclass, as described in Apple’s documentation here. Constraining scrolling from a view subclass can be useful, but what if you want to just temporarily disable scrolling?

The easiest way to disable scrolling is to subclass NSScrollView and add a BOOL property with a name like scrollingEnabled. Then you can simply override the designated initializers (initWithFrame: and initWithCoder:) and set the ivar of the property to YES as the default value:

-(instancetype)initWithFrame:(NSRect)frameRect 
{ 
   self = [super initWithFrame:frameRect]; 
   if (self) 
   { 
       [self setUpOnInit]; 
   } 
   return self; 
}
 
- (instancetype)initWithCoder:(NSCoder *)coder 
{ 
    self = [super initWithCoder:coder]; 
    if (self) 
    { 
         [self setUpOnInit]; 
    } 
    return self; 
}
 
-(void)setUpOnInit
{
   // Set all default values. 
   _scrollingEnabled = YES; 
}

Now you can override the scrollWheel: method and check the property:

-(void)scrollWheel:(NSEvent *)theEvent 
{ 
    if (self.scrollingEnabled) {  
      [super scrollWheel:theEvent];  
     } 
    else {  
       // scrolling is disabled. 
    } 
}

This technique will work in most cases, but it isn’t enough to completely disable scrolling. If there is a view inside of a scroll view that implements autoscrolling behavior (if the view is a dragging destination) the scrollWheel: override won’t block scrolling during an attempted drag and drop operation. To disable scrolling in all cases, you should also subclass NSClipView and block scrolling by overriding the -constrainBoundsRect: method like this:

-(NSRect)constrainBoundsRect:(NSRect)proposedBounds
{
    MyScrollViewSubclass *mainScrollView = (MyScrollViewSubclass*)self.superview;
 
    if (mainScrollView.scrollingEnabled)
    {
        return [super constrainBoundsRect:proposedBounds];
    }
    else
    {
        // Disabled
        return self.documentVisibleRect;
    }
}

After you do that, scrolling should be disabled whenever you set the scrollingEnabled property to NO. But there may be cases where the scroll bar remains visible even when you have scrolling disabled (depending on what type of mouse is being used). To deal with this you should also make sure you set the hasVerticalScroller property to NO when you disable scrolling. If desired, you can implement the setter of the scrollingEnabled property and set hasVerticalScroller at the same time.

How to Deprecate your Own API in Objective-C

To mark some of your own API as deprecated in Objective-C, you simply can add the following attribute to your method declaration (as seen in the snippet below):

-(void)myOldMethod __attribute((deprecated("Use the myNewMethod instead.")));

Now when you call myOldMethod in your code, the compiler will warn you that: “myOldMethod is deprecated. Use the myNewMethod instead”. You can also add the deprecated attribute to property declarations.

But what if you wanted to be more specific? For instance, if there is a better way to handle something in the iOS 8 SDK, you can mark an old method as deprecated only for projects that have iOS 8.0 or later as the deployment target.

-(void)myOldMethod NS_DEPRECATED_IOS(3_0, 8_0,"Use myNewMethod instead.");

Using the code snippet above, if you call myOldMethod the compiler will only warn you that the method is deprecated if your project’s deployment target is set to iOS 8 or later.

Deprecating your own methods can come in handy especially if you have written your own framework that is used across multiple projects. You may choose to deprecate some of your own API rather than removing the methods entirely until you get around to modifying your existing projects to use your new API.