Is it possible to group items in a NSPopupButton?

2019-08-09 04:43发布

问题:

I'm trying to display a list of system voices, but I'd like to group them by region.

This is an example of select in html.

Ideally I'd like to create a dropdown that is similar to accessibility language selection.

Is there any way to replicate this in Interface Builder / swift? Any pointers would be appreciated.

Update:

The reason for this, is because I am displaying a list of speech voices to the user. At the moment, it mixes all the regions together, which is very confusing.

There is an update I'm working on, where I can display "English (United Kingdom)", but I'd like to group them up before releasing it.

回答1:

The following code groups menu, but not like the way you mentioned.

let items = [["First","Second"],["First","Second"],["First","Second"]]

lazy var addNewViewButton : NSPopUpButton = {
    let popupButton = NSPopUpButton()

    let firstMenuItem = NSMenuItem(title: "First Group", action: nil, keyEquivalent: "")
    let secondMenuItem = NSMenuItem(title: "Second Group", action: nil, keyEquivalent: "")
    let thirdMenuItem = NSMenuItem(title: "Third Group", action: nil, keyEquivalent: "")

    let superMenu = NSMenu()
    superMenu.addItem(firstMenuItem)
    superMenu.addItem(secondMenuItem)
    superMenu.addItem(thirdMenuItem)

    for (index,item) in items.enumerated()
    {
        let menu = NSMenu()
        for title in item
        {
            let menuItem = NSMenuItem(title: title, action: nil, keyEquivalent: "")
            menuItem.target = self
            menu.addItem(menuItem)
        }
        menu.addItem(NSMenuItem.separator())
        superMenu.setSubmenu(menu, for: superMenu.items[index])
    }
    popupButton.menu = superMenu

    popupButton.translatesAutoresizingMaskIntoConstraints = false

    return popupButton
}()

Add the popupbutton in your code and you will get results like this

Each one will be having its own items inside.



回答2:

If you don't have the group heading, you can use the following code

let items = [["First","Second"],["First","Second"],["First","Second"]]

lazy var addNewViewButton : NSPopUpButton = {
    let popupButton = NSPopUpButton()

    let menu = NSMenu()
    for item in items
    {
        for title in item
        {
            let menuItem = NSMenuItem(title: title, action: nil, keyEquivalent: "")
            menuItem.target = self
            menu.addItem(menuItem)
        }
        menu.addItem(NSMenuItem.separator())
    }
    popupButton.menu = menu

    popupButton.translatesAutoresizingMaskIntoConstraints = false

    return popupButton
}()

Result :



回答3:

I'm going to offer an answer in Objective-C because I'm not up on Swift. Sorry... You ought to be able to translate it easily enough, though, or I'm sure someone here can do it.

So, I managed this by making a subclass of NSMenu with the following method:

@implementation MenuWithSections

- (NSMenuItem*)insertItemWithTitle:(NSString*)aString action:(SEL)aSelector keyEquivalent:(NSString*)keyEquiv atIndex:(NSInteger)index
{
    NSMenuItem * item;
    NSString * adjustedString;

    if ([aString isEqualToString:menuDividerString]) {
        NSMenuItem *separator = [NSMenuItem separatorItem];
        [self insertItem:separator atIndex:index];
        return separator;
    } else if ([aString hasPrefix:menuSectionHeaderPrefix]) {
        adjustedString = [[aString substringFromIndex:menuSectionHeaderPrefix.length] capitalizedString];
        NSMenuItem * sectionHead =  [[NSMenuItem alloc] initWithTitle:adjustedString
                                                           action:nil
                                                    keyEquivalent:@""];
        sectionHead.enabled = NO;
        sectionHead.indentationLevel = 0;
        [self insertItem:sectionHead atIndex:index];
        return sectionHead;
    } 

    item = [super insertItemWithTitle:aString action:aSelector keyEquivalent:keyEquiv atIndex:index];
    item.indentationLevel = 1;
    return item;
}

@end

menuDividerString in my case is @"----", and menuSectionHeaderPrefix is @".."; see the examples below.

To make it work, do three things:

  1. In IB, drill down the popup button until you see its menu, and set the class of the menu to 'MenuWithSections'. popup button drill-down

  2. Turn off the popup button's 'autoenable' feature (click it off in the 'Attributes' tab of IB, or set it to NO programmatically)

  3. Pass in modified strings using the above constants to get the desired effect.

In other words, to get the look you want in your question, pass the popup button titles like so:

[self.popbutton addItemsWithTitles:@[ @"..swedish cars", @"Volvo", @"Saab", @"..German cars", @"Mercedes", @"Audi"]];

sectioned popup

or if you want a divider-line between the two groups, use:

[self.popbutton addItemsWithTitles:@[ @"..swedish cars", @"Volvo", @"Saab", @"----", @"..German cars", @"Mercedes", @"Audi"]];

sectioned popup with divider