January 27, 2012

ビューベースのNSTableViewで行の高さを設定する

ビューベースの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

実行結果

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

© Shun Takebayashi