NSTableView to allow user to choose which columns to display

I have implemented this and the following should be usable without any subclassing.

First implement an empty menu in IB and connect to the menu output of the Table Header View.

This method (called from awakeFromNib) constructs the contents of the menu from the header (and includes a test to prevent users hiding a primary column)

- (void)initViewHeaderMenu:(id)view {
    //create our contextual menu
    NSMenu *menu = [[view headerView] menu];
    //loop through columns, creating a menu item for each
    for (NSTableColumn *col in [view tableColumns]) {
        if ([[col identifier] isEqualToString:COLUMNID_NAME])
            continue;   // Cannot hide name column
        NSMenuItem *mi = [[NSMenuItem alloc] initWithTitle:[col.headerCell stringValue]
                                                    action:@selector(toggleColumn:)  keyEquivalent:@""];
        mi.target = self;
        mi.representedObject = col;
        [menu addItem:mi];
    }
    return;
}

This invokes the following to do the actual hiding/unhiding

- (void)toggleColumn:(id)sender {
    NSTableColumn *col = [sender representedObject];
    [col setHidden:![col isHidden]];
}

You also need to set the delegate of the Menu and implement the following to set states:-

#pragma mark NSMenu Delegate Methods
-(void)menuWillOpen:(NSMenu *)menu {
    for (NSMenuItem *mi in menu.itemArray) {
        NSTableColumn *col = [mi representedObject];
        [mi setState:col.isHidden ? NSOffState : NSOnState];
    }
}

I extended Milliways' great answer based on this blog post and added the following functionality:

  • Display checkmarks for visible columns
  • Persist the settings using NSUserDefaults

Initial Setup:

// Your intial Startup code
[self setupHeaderMenu:self.yourTableView];

Creating the menu:

Important: Because of col.identifier you'll have to set an "Identify Identifier" for each Table view column in IB for this to work.

#pragma mark - Show Hide Columns
- (void)setupHeaderMenu:(NSTableView *)tableView {

    NSDictionary *savedCols = [[NSUserDefaults standardUserDefaults] dictionaryForKey:kUserDefaultsKeyVisisbleColumns];

    NSMenu *menu = [NSMenu new];
    for (NSTableColumn *col in tableView.tableColumns) {

        NSMenuItem *mi = [[NSMenuItem alloc] initWithTitle:[col.headerCell stringValue]
                                                    action:@selector(toggleColumn:)
                                             keyEquivalent:@""];
        mi.target = self;

        if(savedCols){
            BOOL isVisible = [savedCols[col.identifier] boolValue];
            [col setHidden:!isVisible];
        }

        mi.state = (col.isHidden ? NSOffState: NSOnState);
        mi.representedObject = col;
        [menu addItem:mi];
    }

    tableView.headerView.menu = menu;
    return;
}

The toggle method

The toggle method saves the new configuration in NSUserDefaults

- (void)toggleColumn:(NSMenuItem *)menu {
    NSTableColumn *col = menu.representedObject;

    BOOL shouldHide = !col.isHidden;
    [col setHidden:shouldHide];

    menu.state = (col.isHidden ? NSOffState: NSOnState);

    NSMutableDictionary *cols = @{}.mutableCopy;
    for( NSTableColumn *column in self.yourTableView.tableColumns){
        cols[column.identifier] = @(!column.isHidden);
    }

    [[NSUserDefaults standardUserDefaults] setObject:cols forKey:kUserDefaultsKeyVisibleColumns];
    if(shouldHide){
        [self.yourTableView sizeLastColumnToFit];
    } else {
        [self.yourTableView sizeToFit];
    }
}

Menu delegate

-(void)menuWillOpen:(NSMenu *)menu {
    for (NSMenuItem *mi in menu.itemArray) {
        NSTableColumn *col = [mi representedObject];
        [mi setState:col.isHidden ? NSOffState : NSOnState];
    }
}

The Result

So now you can check / uncheck each column and the configuration will be saved even after a restart of your App.

hide columns


You need to attach an NSMenu to the header of the table that has the columns as NSMenuItems and pop it open on right click. I've done this by subclassing NSViewController and attaching my table view in it. The class should also be an NSMenuDelegate. Example below.

.h file:

@interface UserManagedColumnsTableViewController : NSViewController <NSMenuDelegate>
@property (weak) IBOutlet NSTableView *tableView;
@end

.m file:

@interface UserManagedColumnsTableViewController ()
- (void)toggleColumn:(id)sender;
@end

@implementation UserManagedColumnsTableViewController

- (void)awakeFromNib {
  [super awakeFromNib];
  NSMenu *columnsMenu = [[NSMenu alloc] initWithTitle:@""];
  for (NSTableColumn *column in self.tableView.tableColumns) {
    NSMenuItem *menuItem = [[NSMenuItem alloc] initWithTitle:[column.headerCell stringValue]
                                                      action:@selector(toggleColumn:)
                                               keyEquivalent:@""];
    menuItem.target = self;
    menuItem.representedObject = column;
    [columnsMenu addItem:menuItem];
  }
  columnsMenu.delegate = self;
  [self.tableView.headerView setMenu:columnsMenu];
}

#pragma mark - NSMenuDelegate conformance
- (void)menuWillOpen:(NSMenu *)menu {
  for (NSMenuItem *menuItem in menu.itemArray) {
    NSTableColumn *column = [menuItem representedObject];
    [menuItem setState:column.isHidden ? NSOffState : NSOnState];
  }
}

#pragma mark - Private Methods
- (void)toggleColumn:(id)sender {
  NSTableColumn *column = [sender representedObject];
  [column setHidden:![column isHidden]];
}

@end