I have an NSTableView which is bound to an NSArrayController. I would like to have one of the table columns showing the index of the table row. This is easy enough to do when yo
As I understand it, you could choose not to bind that table column, and use a datasource instead. I recall NSTableView supports this sort of "dual mode" operation, but can't find any docs to confirm it.
Assuming that you aim to replicate the behavior of iTunes, your column only needs to display 1 to (number of visible rows). It does not actually need to relate to your model at all.
So, give your controller a dynamically-generated array property, and make it depend on the array of actual model objects.
For this property, implement the array accessor methods, with the indexed getter simply computing idx + 1
, boxing it up, and returning that object. You may also need to implement the whole-array getter to satisfy KVC.
Make sure the column is set as non-editable, and the binding set to not conditionally set enabled
. Otherwise, you'll get an exception (for not having setters for this property) when the user tries to enter a new index for a row.
One note of caution: This may cause problems, as NSTableView doesn't expect its columns to be bound to discrete arrays; it expects all of its columns to be bound to different properties of the model objects in a single array. You may need to bind the content
of the table view itself to your array of model objects, in addition to binding the content bindings of the columns, and I've had trouble with that before.
I recently implemented this using an NSRuler subclass that draws the line numbers next to each line in the TableView. I based the code on something similar that I found here.
You can add this to your tableview using:
NSScrollView *scrollView = [tableView enclosingScrollView];
TableLineNumberRulerView *lineNumberView = [[TableLineNumberRulerView alloc] initWithTableView:tableView
usingArrayController:arrayController];
[scrollView setVerticalRulerView:lineNumberView];
[scrollView setHasVerticalRuler:YES];
[scrollView setRulersVisible:YES];
Here's the interface file:
//
// TableLineNumberRulerView
// Line View Test
//
// Created by Ben Golding, Object Craft Pty Ltd on 7 May 2014.
// Based on code by Paul Kim on 9/28/08.
#import <Cocoa/Cocoa.h>
@interface TableLineNumberRulerView : NSRulerView<NSCoding>
@property (strong) NSArrayController *arrayController;
@property (strong) NSFont *font;
@property (strong) NSColor *textColor;
@property (strong) NSColor *alternateTextColor;
@property (strong) NSColor *backgroundColor;
@property (strong) NSDictionary *textAttributes;
@property (assign) NSUInteger rowCount;
- (id)initWithTableView:(NSTableView *)tableView usingArrayController:(NSArrayController *)arrayController;
@end
Here's the implementation:
//
// TableLineNumberRulerView.m
// Line View Test
//
// Created by Ben Golding, Object Craft Pty Ltd on 7 May 2014.
// Based on code by Paul Kim on 9/28/08.
#import "TableLineNumberRulerView.h"
#define DEFAULT_THICKNESS 22.0
#define RULER_MARGIN 5.0
@implementation TableLineNumberRulerView
@synthesize font;
@synthesize textColor;
@synthesize alternateTextColor;
@synthesize backgroundColor;
@synthesize textAttributes;
@synthesize rowCount;
- (id)initWithTableView:(NSTableView *)tableView usingArrayController:(NSArrayController *)arrayController
{
NSScrollView *scrollView = [tableView enclosingScrollView];
if ((self = [super initWithScrollView:scrollView orientation:NSVerticalRuler]) == nil)
return nil;
[self setClientView:tableView];
self.arrayController = arrayController;
[arrayController addObserver:self forKeyPath:@"arrangedObjects" options:NSKeyValueObservingOptionNew context:nil];
self.font = [NSFont labelFontOfSize:[NSFont systemFontSizeForControlSize:NSMiniControlSize]];
self.textColor = [NSColor colorWithCalibratedWhite:0.42 alpha:1.0];
self.alternateTextColor = [NSColor whiteColor];
self.textAttributes = @{
NSFontAttributeName: [self font],
NSForegroundColorAttributeName: [self textColor]
};
self.rowCount = [[arrayController arrangedObjects] count];
return self;
}
- (void)awakeFromNib
{
[self setClientView:[[self scrollView] documentView]]; // this will be an NSTableView instance
}
- (void)finalize
{
[self.arrayController removeObserver:self forKeyPath:@"arrangedObjects"];
}
#pragma mark -
#pragma mark Key-Value observing of changes to array controller
/*
* This picks up changes to the arrayController's arrangedObjects using KVO.
* We check the size of the old and new rowCounts and compare them to see if the number
* digits has changed, and if so, we adjust the ruler width.
*/
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if ([keyPath isEqualToString:@"arrangedObjects"]) {
NSUInteger newRowCount = [[self.arrayController arrangedObjects] count];
if ((int)log10(self.rowCount) != (int)log10(newRowCount))
[self setRuleThickness:[self requiredThickness]];
self.rowCount = newRowCount;
// we need to redisplay because line numbers may change or disappear in view
[self setNeedsDisplay:YES];
}
}
- (CGFloat)requiredThickness
{
NSUInteger lineCount = [[self.arrayController arrangedObjects] count],
digits = (unsigned)log10((lineCount < 1) ? 1: lineCount) + 1;
NSMutableString *sampleString = [NSMutableString string];
NSSize stringSize;
for (NSUInteger i = 0; i < digits; i++) {
// Use "8" since it is one of the fatter numbers. Anything but "1"
// will probably be ok here. I could be pedantic and actually find the fattest
// number for the current font but nah.
[sampleString appendString:@"8"];
}
stringSize = [sampleString sizeWithAttributes:[self textAttributes]];
// Round up the value. There is a bug on 10.4 where the display gets all wonky when scrolling if you don't
// return an integral value here.
return ceil(MAX(DEFAULT_THICKNESS, stringSize.width + RULER_MARGIN * 2));
}
- (void)drawHashMarksAndLabelsInRect:(NSRect)aRect
{
NSTableView *tableView = (NSTableView *)[self clientView];
NSRect bounds = [self bounds];
NSRect visibleRect = [[tableView enclosingScrollView] documentVisibleRect];
NSRange visibleRowRange = [tableView rowsInRect:visibleRect];
CGFloat yinset = NSHeight([[tableView headerView] bounds]);
if (backgroundColor != nil) {
[backgroundColor set];
NSRectFill(bounds);
[[NSColor colorWithCalibratedWhite:0.58 alpha:1.0] set];
[NSBezierPath strokeLineFromPoint:NSMakePoint(NSMaxX(bounds) - 0/5, NSMinY(bounds))
toPoint:NSMakePoint(NSMaxX(bounds) - 0.5, NSMaxY(bounds))];
}
// NSLog(@"drawHashMarksAndLabelsInRect: bounds %@, ruleThickness %lf", NSStringFromRect(bounds), [self ruleThickness]);
for (NSUInteger row = visibleRowRange.location; NSLocationInRange(row, visibleRowRange); row++) {
// Line numbers are internally stored starting at 0
NSString *labelText = [NSString stringWithFormat:@"%lu", row + 1];
NSSize stringSize = [labelText sizeWithAttributes:self.textAttributes];
NSRect rowRect = [tableView rectOfRow:row];
CGFloat ypos = yinset + NSMinY(rowRect) - NSMinY(visibleRect);
[labelText drawInRect:NSMakeRect(NSWidth(bounds) - stringSize.width - RULER_MARGIN,
ypos + (NSHeight(rowRect) - stringSize.height) / 2.0,
NSWidth(bounds) - RULER_MARGIN * 2.0, NSHeight(rowRect))
withAttributes:self.textAttributes];
}
}
#pragma mark -
#pragma mark NSCoding methods
#define FONT_CODING_KEY @"font"
#define TEXT_COLOR_CODING_KEY @"textColor"
#define ALT_TEXT_COLOR_CODING_KEY @"alternateTextColor"
#define BACKGROUND_COLOR_CODING_KEY @"backgroundColor"
- (id)initWithCoder:(NSCoder *)decoder
{
if ((self = [super initWithCoder:decoder]) != nil) {
if ([decoder allowsKeyedCoding]) {
font = [decoder decodeObjectForKey:FONT_CODING_KEY];
textColor = [decoder decodeObjectForKey:TEXT_COLOR_CODING_KEY];
alternateTextColor = [decoder decodeObjectForKey:ALT_TEXT_COLOR_CODING_KEY];
backgroundColor = [decoder decodeObjectForKey:BACKGROUND_COLOR_CODING_KEY];
} else {
font = [decoder decodeObject];
textColor = [decoder decodeObject];
alternateTextColor = [decoder decodeObject];
backgroundColor = [decoder decodeObject];
}
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)encoder
{
[super encodeWithCoder:encoder];
if ([encoder allowsKeyedCoding]) {
[encoder encodeObject:font forKey:FONT_CODING_KEY];
[encoder encodeObject:textColor forKey:TEXT_COLOR_CODING_KEY];
[encoder encodeObject:alternateTextColor forKey:ALT_TEXT_COLOR_CODING_KEY];
[encoder encodeObject:backgroundColor forKey:BACKGROUND_COLOR_CODING_KEY];
} else {
[encoder encodeObject:font];
[encoder encodeObject:textColor];
[encoder encodeObject:alternateTextColor];
[encoder encodeObject:backgroundColor];
}
}
@end