ビューベースのNSTableViewでは、セルビューや行ビューとして複数の種類のビューを使い分けることができます。例えば、下のコードではデータソースに文字列と画像を混在させ、文字列であればNSTextFieldを、画像であればNSImageViewをセル内に表示させます。

@interface TableDataSource : NSObject <NSTableViewDataSource, NSTableViewDelegate>
@end
@implementation TableDataSource {
NSArray *_items;
}
- (id)init {
self = [super init];
if (self) {
_items = [NSArray arrayWithObjects:
@"Hello",
[NSImage imageNamed:@"NSApplicationIcon"],
@"World",
nil];
}
return self;
}
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView {
return [_items count];
}
- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
id item = [_items objectAtIndex:row];
NSTableCellView *view;
if ([item isKindOfClass:[NSString class]]) {
view = [tableView makeViewWithIdentifier:@"TextRow" owner:self];
view.textField.stringValue = item;
}
else if ([item isKindOfClass:[NSImage class]]) {
view = [tableView makeViewWithIdentifier:@"ImageRow" owner:self];
view.imageView.image = item;
}
return view;
}
@end

XIBはこんな感じ。文字列用のセルビューは小さめ、画像用のセルビューは大きめにしました。

ところが、これを実行すると、文字列用と画像用のセルビューの高さが一緒になってしまいます。

どうやら、XIB上でのセルビューの高さは無視されて(というより-tableView:viewForTableColumn:で返したビューの高さを無視して)、全てのセルビューの中で最も大きい高さのものに勝手に揃えられてしまうようです。

これでは困るので、-tableView:heightOfRow:を実装して行ごとの高さを返すことにします。先ほどのコードを次のように書き換えました。

@implementation TableDataSource {
NSArray *_items;
NSMutableDictionary *_heights;
}
- (id)init {
self = [super init];
if (self) {
_items = [NSArray arrayWithObjects:
@"Hello",
[NSImage imageNamed:@"NSApplicationIcon"],
@"World",
nil];
_heights = [NSMutableDictionary dictionary];
}
return self;
}
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView {
return [_items count];
}
- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
id item = [_items objectAtIndex:row];
NSTableCellView *view;
if ([item isKindOfClass:[NSString class]]) {
view = [tableView makeViewWithIdentifier:@"TextRow" owner:self];
view.textField.stringValue = item;
}
else if ([item isKindOfClass:[NSImage class]]) {
view = [tableView makeViewWithIdentifier:@"ImageRow" owner:self];
view.imageView.image = item;
}
return view;
}
- (CGFloat)tableView:(NSTableView *)tableView heightOfRow:(NSInteger)row {
id item = [_items objectAtIndex:row];
if (!_heights) {
_heights = [NSMutableDictionary dictionary];
}
NSString *identifier = [item isKindOfClass:[NSString class]] ? @"TextRow" : @"ImageRow";
NSNumber *height = [_heights objectForKey:identifier];
if (!height) {
NSView *view = [tableView makeViewWithIdentifier:identifier owner:self];
height = [NSNumber numberWithDouble:view.bounds.size.height];
[_heights setObject:height forKey:identifier];
}
return height.doubleValue;
}
@end

実行結果

少し強引ですが、一度ビューを実際に生成してみて、その高さを保持するようにしています。もう少しスマートな方法がありそうな気がしますが、今回はとりあえずこれで行くことにします。