The contents of a NSPopupButton
are bound to an NSArray
of strings.
How can we insert a separator item via bindings?
The "-
" strings (like in the olden/Classic days) doesn't work, i.e. shows up literally as a "-
" menu item.
Is there any out-of-the-box solution with standard Cocoa classes and bindings?
This should be a trivial problem but I can't find any solution to the problem that doesn't involve silly hacks like subclassing NSMenu
, NSPopupButton
or other non-intuitive work arounds.
I couldn't find a clean way to dynamically add separators to a menu when using bindings. The easiest (and most reusable) way I've found is to use an NSMenuDelegate to dynamically swap out NSMenuItems with a specific title like @"---"
with separator items in the menuNeedsUpdate:
delegate method.
Step 1: Create an NSObject that conforms to NSMenuDelegate protocol
#import <Cocoa/Cocoa.h>
@interface SeparatorMenuDelegate : NSObject <NSMenuDelegate>
@end
@implementation SeparatorMenuDelegate
-(void)menuNeedsUpdate:(NSMenu *)menu {
NSArray* fakeSeparators = [[menu itemArray] filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"title == '---'"]];
for (NSMenuItem* fakeSep in fakeSeparators) {
[menu insertItem:[NSMenuItem separatorItem] atIndex:[menu indexOfItem:fakeSep]];
[menu removeItem:fakeSep];
}
}
@end
Step 2: Link things up in Interface Builder.
Drag out an Object into the scene that contains the NSPopupButton instance.
Set the object's class to SeparatorMenuDelegate
Twirl open the NSPopupButton control in the Document Outline and select the Menu inside it. Then set the delegate for the Menu to the SeparatorMenuDelegate
object that you dragged in earlier.
After this, all items in the menu with a title of @"---" will be converted to separator items.
If you have multiple NSPopupButton instances in the same scene, you can set the delegate of their Menu to the same object (you only need one SeparatorMenuDelegate
per scene).
IMHO, the cleanest solution is still subclassing NSMenu – this kind of customization is exactly what subclassing is for. The following solution is based on what @matt wrote many years ago on Cocoabuilder and is updated to be more universally applicable, including on High Sierra.
First, define a “magic string” to represent the separator item in your code; do this in a header file that all affected classes will import. In this example, I’ve chosen “---”, but of course, this can be any string you like:
#define MY_MENU_SEPARATOR @"---"
Second, subclass NSMenu and overwrite the two methods that add menu items, in order to handle the special separator case:
@implementation MyMenu
- (NSMenuItem*)addItemWithTitle:(NSString*)aString action:(SEL)aSelector keyEquivalent:(NSString*)keyEquiv
{
if ([aString isEqualToString:MY_MENU_SEPARATOR])
{
NSMenuItem *separator = [NSMenuItem separatorItem];
[self addItem:separator];
return separator;
}
return [super addItemWithTitle:aString action:aSelector keyEquivalent:keyEquiv];
}
- (NSMenuItem*)insertItemWithTitle:(NSString*)aString action:(SEL)aSelector keyEquivalent:(NSString*)keyEquiv atIndex:(NSInteger)index
{
if ([aString isEqualToString:MY_MENU_SEPARATOR])
{
NSMenuItem *separator = [NSMenuItem separatorItem];
[self insertItem:separator atIndex:index];
return separator;
}
return [super insertItemWithTitle:aString action:aSelector keyEquivalent:keyEquiv atIndex:index];
}
@end
And that’s it. Set the affected menus to be of the MyMenu class in the Identity inspector of Interface Builder, and they will insert separator items where desired. Works for menu bar menus as well as for pop-ups.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With