How to Create a View-Based Nstableview Purely in Code

How does the NSTableView set content Mode(view-based or cell-based) by code?

NSTableView defaults to being cell-based, which makes sense for backwards compatibility. Table views are view-based when the table view delegate implements -tableView:viewForTableColumn:row:. You can easily test by programmatically creating a table view as follows:

@implementation BAVAppDelegate

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
NSView *contentView = self.window.contentView;
NSTableView *tableView = [[NSTableView alloc] initWithFrame:(NSRect){{50, NSMaxY(contentView.frame) - 200}, {400, 200}}];
tableView.dataSource = self;
tableView.delegate = self;
[contentView addSubview:tableView];

NSTableColumn *column = [[NSTableColumn alloc] initWithIdentifier:@"column"];
column.width = 400;
[tableView addTableColumn:column];
}

- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView {
return 3;
}

- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
return [NSString stringWithFormat:@"%ld", row];
}

//- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
// NSTextField *textField = [[NSTextField alloc] initWithFrame:(NSRect){.size = {100, 15}}];
// textField.stringValue = [NSString stringWithFormat:@"%ld", row];
// return textField;
//}

@end

If you run this code with that delegate method commented out, you get a cell-based table view:

Sample Image

And if you uncomment that delegate method, you get a view-based table view:

Sample Image

The documentation for -tableView:viewForTableColumn:row: states that

This method is required if you wish to use NSView objects instead of NSCell objects for the cells within a table view. Cells and views can not be mixed within the same table view.

which hints at it being the condition that determines whether a table view is cell- or view-based.

View-based NSTableView with rows that have dynamic heights

This is a chicken and the egg problem. The table needs to know the row height because that determines where a given view will lie. But you want a view to already be around so you can use it to figure out the row height. So, which comes first?

The answer is to keep an extra NSTableCellView (or whatever view you are using as your "cell view") around just for measuring the height of the view. In the tableView:heightOfRow: delegate method, access your model for 'row' and set the objectValue on NSTableCellView. Then set the view's width to be your table's width, and (however you want to do it) figure out the required height for that view. Return that value.

Don't call noteHeightOfRowsWithIndexesChanged: from in the delegate method tableView:heightOfRow: or viewForTableColumn:row: ! That is bad, and will cause mega-trouble.

To dynamically update the height, then what you should do is respond to the text changing (via the target/action) and recalculate your computed height of that view. Now, don't dynamically change the NSTableCellView's height (or whatever view you are using as your "cell view"). The table must control that view's frame, and you will be fighting the tableview if you try to set it. Instead, in your target/action for the text field where you computed the height, call noteHeightOfRowsWithIndexesChanged:, which will let the table resize that individual row. Assuming you have your autoresizing mask setup right on subviews (i.e.: subviews of the NSTableCellView), things should resize fine! If not, first work on the resizing mask of the subviews to get things right with variable row heights.

Don't forget that noteHeightOfRowsWithIndexesChanged: animates by default. To make it not animate:

[NSAnimationContext beginGrouping];
[[NSAnimationContext currentContext] setDuration:0];
[tableView noteHeightOfRowsWithIndexesChanged:indexSet];
[NSAnimationContext endGrouping];

PS: I respond more to questions posted on the Apple Dev Forums than stack overflow.

PSS: I wrote the view based NSTableView

static NSTableView cell in Cocoa

After a day's struggle, I figured out how to display a static tableView Cell. All you need to do is in the XIB, add the identifier for all the cell's that you want to display and then implement the two required dataSource numberOfRowsInTableView: and tableView:viewForTableColumn:row:. You should now be able to display the Static TableView Cell.

View-based NSOutlineView without NIB?

If you follow the example in SidebarDemo, they use a subclass of NSTableCellView for the detail rows. In order to emulate the InterfaceBuilder mojo, you can hook everything together in the constructor. The rest is the same as the demo (see outlineView:viewForTableColumn:item:).

@interface SCTableCellView : NSTableCellView
@end

@implementation SCTableCellView

- (id)initWithFrame:(NSRect)frameRect {
self = [super initWithFrame:frameRect];
[self setAutoresizingMask:NSViewWidthSizable];
NSImageView* iv = [[NSImageView alloc] initWithFrame:NSMakeRect(0, 6, 16, 16)];
NSTextField* tf = [[NSTextField alloc] initWithFrame:NSMakeRect(21, 6, 200, 14)];
NSButton* btn = [[NSButton alloc] initWithFrame:NSMakeRect(0, 3, 16, 16)];
[iv setImageScaling:NSImageScaleProportionallyUpOrDown];
[iv setImageAlignment:NSImageAlignCenter];
[tf setBordered:NO];
[tf setDrawsBackground:NO];
[[btn cell] setControlSize:NSSmallControlSize];
[[btn cell] setBezelStyle:NSInlineBezelStyle];
[[btn cell] setButtonType:NSMomentaryPushInButton];
[[btn cell] setFont:[NSFont boldSystemFontOfSize:10]];
[[btn cell] setAlignment:NSCenterTextAlignment];
[self setImageView:iv];
[self setTextField:tf];
[self addSubview:iv];
[self addSubview:tf];
[self addSubview:btn];
return self;
}

- (NSButton*)button {
return [[self subviews] objectAtIndex:2];
}

- (void)viewWillDraw {
[super viewWillDraw];
NSButton* btn = [self button];
...

NSTableView inside an NSTableViewCell

Update: Using TableViews

You can also use NSTableView for what you want do achieve. Unlike UITableView, NSTableView does not do the scrolling itself. It is wrapped inside an NSClipView, which itself is wrapped in an NSScrollView. So you simply extract the tableView out of that and add some constraints.

Interface builder does not support that currently very well. You can't drag the tableView out of its enclosing clipView. But you can open the interface file as source code and remove everything beginning from the scrollView (except the table itself).
The tableView should display fine in the Interface Builder (tested on Xcode 9 and 10)

You have to add the constraints in code, but then the tableView should grow by itself.


Since the inner table view does not have to scroll, you can use just NSStackView to layout your views. Then you don’t have to fight the behaviors of NSTableView.
If you need an example, feel free to ask!



Related Topics



Leave a reply



Submit