Anyone knows how to allow users to choose which columns of an NSTableView to be displayed by right clicking and then selecting? Just like iTunes does.
3 Answers
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];
}
}

- 1,265
- 1
- 12
- 26
-
[`menuWillOpen`](http://developer.apple.com/library/mac/#documentation/cocoa/reference/NSMenuDelegate_Protocol/Reference/Reference.html) *Do not modify ... the menu items during this method.* Perhaps `menuNeedsUpdate` is a better candidate? – ta.speot.is Jan 12 '13 at 00:49
-
2A method name should not start with "init" if it is not an initializer. – cocoafan Apr 02 '14 at 11:39
-
This is brilliant. You might wan to add `mi.state = (col.isHidden ? NSOffState: NSOnState);` to display a checkbox next to visible columns. This needs to be updated after hiding the column in `toggleColumn:` like so `[sender setState: (col.isHidden ? NSOffState: NSOnState)]; – Besi Apr 16 '14 at 08:51
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.

- 2,437
- 21
- 31

- 22,579
- 24
- 131
- 223
-
Hiding columns works, but it fails if you want to have an even width for all columns. I am using "uniform" on my tableview and "resizes with tableview" on my columns. After unchecking/checking a few the width gets uneven. Any idea how this can be solved? – David Aug 08 '14 at 06:57
-
Awesome! This works beautifully! The only thing I needed to add was a default of the columns hidden/shown and it was ready to be used. Thanks! – guitarflow May 24 '15 at 21:30
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

- 9,731
- 1
- 31
- 35