Автоматическая настройка высоты NSTableView
Я задал этот вопрос раньше, но я просто не очень доволен решением.
Автоматически настраивать размер NSTableView
Я хочу показать NSTableView
в NSPopover
или в NSWindow
.
Теперь размер окна должен корректироваться в виде таблицы.
Так же, как Xcode делает это:
![enter image description here]()
![enter image description here]()
Это довольно просто с автоматической компоновкой, вы можете просто привязать внутренний вид к супер-представлению.
Моя проблема в том, что я не могу определить оптимальную высоту табличного представления.
Следующий код перечисляет все доступные строки, но не возвращает правильное значение, потому что в представлении таблицы есть другие элементы, такие как разделители и головка таблицы.
- (CGFloat)heightOfAllRows:(NSTableView *)tableView {
CGFloat __block height;
[tableView enumerateAvailableRowViewsUsingBlock:^(NSTableRowView *rowView, NSInteger row) {
// tried it with this one
height += rowView.frame.size.height;
// and this one
// height += [self tableView:nil heightOfRow:row];
}];
return height;
}
1. Вопрос
Как я могу это исправить? Как я могу правильно вычислить требуемую высоту представления таблицы.
2. Вопрос
Где я должен запускать этот код?
Я не хочу реализовывать это в контроллере, потому что это определенно то, что представление таблицы должно обрабатывать сам.
И я даже не нашел полезных методов делегирования.
Поэтому я решил, что лучше всего было бы, если бы вы могли подклассы NSTableView
.
Итак, мой вопрос 2, где его реализовать?
Мотивация
Определенно стоит награда
Ответы
Ответ 1
Вы можете запросить кадр последней строки, чтобы получить высоту таблицы:
- (CGFloat)heightOfTableView:(NSTableView *)tableView
{
NSInteger rows = [self numberOfRowsInTableView:tableView];
if ( rows == 0 ) {
return 0;
} else {
return NSMaxY( [tableView rectOfRow:rows - 1] );
}
}
Это предполагает наличие скрытого вида прокрутки без границ!
Вы можете запросить tableView.enclosingScrollView.borderType
, чтобы проверить, ограничено ли представление прокрутки или нет. Если это так, ширина границы должна быть добавлена к результату (два раза, снизу и сверху). К сожалению, я не знаю, как получить ширину границы.
Преимущество запроса rectOfRow:
заключается в том, что он работает на той же самой итерации runloop, что и [tableView reloadData];
. По моему опыту, запрос к кадру представления таблицы не работает надежно, когда вы сначала выполняете reloadData
(вы получите предыдущую высоту).
Ответ 2
Интерфейс Builder в Xcode автоматически помещает NSTableView в NSScrollView. NSScrollView - это место, где расположены заголовки. Создайте NSScrollView в качестве базового представления в окне и добавьте к нему NSTableView:
NSScrollView * scrollView = [[NSScrollView alloc]init];
[scrollView setHasVerticalScroller:YES];
[scrollView setHasHorizontalScroller:YES];
[scrollView setAutohidesScrollers:YES];
[scrollView setBorderType:NSBezelBorder];
[scrollView setTranslatesAutoresizingMaskIntoConstraints:NO];
NSTableView * table = [[NSTableView alloc] init];
[table setDataSource:self];
[table setColumnAutoresizingStyle:NSTableViewUniformColumnAutoresizingStyle];
[scrollView setDocumentView:table];
//SetWindow main view to scrollView
Теперь вы можете опросить scrollView contentView, чтобы найти размер размера NSScrollView
NSRect rectOfFullTable = [[scrollView contentView] documentRect];
Поскольку NSTableView находится внутри NSScrollView, NSTableView будет иметь заголовок, который вы можете использовать, чтобы найти размер ваших заголовков.
Вы можете подклассифицировать NSScrollView для обновления его супервизора при изменении размера таблицы (заголовки + строки) путем переопределения метода reflectScrolledClipView:
Ответ 3
Я не уверен, что мое решение лучше, чем то, что у вас есть, но я подумал, что все равно предлагаю. Я использую это с видом печати. Я не использую автоматический макет. Он работает только с привязками - потребуется настройка для работы с источником данных.
Вы увидите там ужасный взлом, чтобы заставить его работать: я просто добавляю 0.5 к значению, которое я тщательно вычисляю.
Это учитывает интервал, но не заголовки, которые я не показываю. Если вы показываете заголовки, вы можете добавить это в метод -tableView:heightOfRow:
.
В NSTableView подкласс или категория:
- (void) sizeHeightToFit {
CGFloat height = 0.f;
if ([self.delegate respondsToSelector:@selector(tableView:heightOfRow:)]) {
for (NSInteger i = 0; i < self.numberOfRows; ++i)
height = height +
[self.delegate tableView:self heightOfRow:i] +
self.intercellSpacing.height;
} else {
height = (self.rowHeight + self.intercellSpacing.height) *
self.numberOfRows;
}
NSSize frameSize = self.frame.size;
frameSize.height = height;
[self setFrameSize:frameSize];
}
В представлении представления таблицы:
// Invoke bindings to get the cell contents
// FIXME If no bindings, use the datasource
- (NSString *) stringValueForRow:(NSInteger) row column:(NSTableColumn *) column {
NSDictionary *bindingInfo = [column infoForBinding:NSValueBinding];
id object = [bindingInfo objectForKey:NSObservedObjectKey];
NSString *keyPath = [bindingInfo objectForKey:NSObservedKeyPathKey];
id value = [[object valueForKeyPath:keyPath] objectAtIndex:row];
if ([value isKindOfClass:[NSString class]])
return value;
else
return @"";
}
- (CGFloat) tableView:(NSTableView *) tableView heightOfRow:(NSInteger) row {
CGFloat result = tableView.rowHeight;
for (NSTableColumn *column in tableView.tableColumns) {
NSTextFieldCell *dataCell = column.dataCell;
if (![dataCell isKindOfClass:[NSTextFieldCell class]]) continue;
// Borrow the prototype cell, and set its text
[dataCell setObjectValue:[self stringValueForRow:row column:column]];
// Ask it the bounds for a rectangle as wide as the column
NSRect cellBounds = NSZeroRect;
cellBounds.size.width = [column width]; cellBounds.size.height = FLT_MAX;
NSSize cellSize = [dataCell cellSizeForBounds:cellBounds];
// This is a HACK to make this work.
// Otherwise the rows are inexplicably too short.
cellSize.height = cellSize.height + 0.5;
if (cellSize.height > result)
result = cellSize.height;
}
return result;
}
Ответ 4
Этот ответ предназначен для Swift 4, предназначенный для macOS 10.10 и более поздних версий:
1. Ответ
Вы можете использовать представление таблицы fittingSize
для расчета размера вашего popover.
tableView.needsLayout = true
tableView.layoutSubtreeIfNeeded()
let height = tableView.fittingSize.height
2. Ответ
Я понимаю ваше желание перенести этот код из контроллера представления, но поскольку сам вид таблицы ничего не знает о количестве элементов (только через делегирование) или изменениях в моделях, я бы добавил это в контроллер представления. Начиная с macOS 10.10, вы можете использовать preferredContentSize
на NSViewController
внутри popover, чтобы установить размер.
func updatePreferredContentSize() {
tableView.needsLayout = true
tableView.layoutSubtreeIfNeeded()
let height = tableView.fittingSize.height
let width: CGFloat = 320
preferredContentSize = CGSize(width: width, height: height)
}
В моем примере я использую фиксированную ширину, но вы также можете использовать вычисленную (еще не протестировали ее).
Вы хотите вызвать метод обновления всякий раз, когда ваш источник данных изменяется и/или когда вы собираетесь отображать popover.
Надеюсь, это решает вашу проблему!
Ответ 5
Просто получите -[NSTableView frame]
NSTableView встроен в NSScrollView, но имеет полный размер.