Draw SwiftUI.Form style pop-up button with NSPopUpButton in AppKit

In SwiftUI on macOS, A menu-style Picker is drawn as a pop-up button. It generally looks and behaves the same as an NSPopUpButton in AppKit.

SwiftUI introduced iOS-like looking UI for settings in macOS, and consequently, the Picker also has its own style when placed inside a Form. A Form-style Picker displays only up/down chevrons and draws the background only when the mouse hovers over it. It also changes its width dynamically based on the selected item.

Form {
    Picker("Animal:", selection: $selection) {
        ForEach(["Dog", "Cow"], id: \.self) {
            Text($0)
    }
    .pickerStyle(.menu)
}

You can find it, for instance, in the Print dialog.

My question is: I couldn't find a way to draw an NSPopUpButton in AppKit with this style. Does anyone know how to achieve this in AppKit?

Some might say I should just use SwiftUI straightforwardly, but I would like to use it in a print panel accessory that currently still avoids using SwiftUI but its dialog has SwiftUI.Form-looking.

Answered by 1024jp in 858101022

After my post, I found NSPopUpButton (or NSButton) has a private API named semanticContext for this purpose. Though, I can’t use it since my app is distributed in the App Store...

cf. https://stackoverflow.com/questions/79593493/how-to-achieve-grouped-form-styled-controls-using-appkit/79607626#79607626

Sent feedback: FB18401945.

This should do it.

NSPopUpButton *popupButton = [[NSPopUpButton alloc]initWithFrame:NSZeroRect];
[popupButton addItemsWithTitles:@[@"Animal",@"Dog",@"Cow"]];
popupButton.bezelStyle = NSBezelStyleFlexiblePush;
popupButton.showsBorderOnlyWhileMouseInside = YES;
[popupButton sizeToFit];

It should be easy enough to translate my Objective-C to Swift if needed.

Accepted Answer

After my post, I found NSPopUpButton (or NSButton) has a private API named semanticContext for this purpose. Though, I can’t use it since my app is distributed in the App Store...

cf. https://stackoverflow.com/questions/79593493/how-to-achieve-grouped-form-styled-controls-using-appkit/79607626#79607626

Additionally, the up-down arrows are drawn even when the button isn’t hovered over.

Hmm prior to Tahoe the arrows did draw even when the mouse wasn't hovered inside. Upgrading to Tahoe this does not happen. I just verified this change in behavior in a test project (using the code from my previous post):

  1. Add UIDesignRequiresCompatibility to Info.plist and set it to YES.
  2. Run the app.

Arrows draw even while mouse is not hovering over it.

Change UIDesignRequiresCompatibility entry back to NO and the arrows do not remain. So they changed stuff in macOS 26. Maybe there is a way you to get the pre Tahoe behavior if you play with some of the button properties. Someone from Apple should know.

If there is no way to get this behavior for "free" on Tahoe you could always subclass. I don't think it should be too hard.

. In the SwiftUI.Form style, the pop-up button shrinks its width to match the width of each option when the selection changes

IMO I don't think a pop up button should adjust its width based on its selection b/c that can create a moving target for the user. I think it should take the width of its widest entry, or settle on a reasonable width and if there is an option with a long title let it truncate if that one option is selected. But what do I know I'm just a peasant developer :)

You could resize on selection change if you want though.

Draw SwiftUI.Form style pop-up button with NSPopUpButton in AppKit
 
 
Q