У меня есть приложение с view-based NSTableView
в нем. Внутри этого табличного представления у меня есть строки с ячейками, содержимое которых состоит из нескольких строк NSTextField
с включенным переносом слов. В зависимости от текстового содержимого NSTextField
, размер строк, необходимых для отображения ячейки, будет варьироваться.
Я знаю, что могу реализовать NSTableViewDelegate
метод - tableView:heightOfRow:
вернуть высоту, но высота будет определяться на основе переноса слов, используемого в NSTextField
. Перенос слов NSTextField
аналогичным образом основан на ширине NSTextField
..., которая определяется шириной NSTableView
.
Тааааааааааааааааааааааааалов или)))) вот в чем заключается мой вопрос. Какой шаблон проектирования для этого подходит? Кажется, все, что я пробую, превращается в запутанный беспорядок. Поскольку TableView требует знания высоты ячеек для их расположения ... и NSTextField
знания его макета для определения переноса слов… а ячейке требуется знание переноса слов, чтобы определить его высоту… это круговой беспорядок… и это сводит меня с ума .
Предложения?
Если это имеет значение, конечный результат также будет доступен для редактирования NSTextFields
, размер которого будет изменяться в соответствии с текстом внутри них. У меня уже есть эта работа на уровне представления, но представление таблицы еще не регулирует высоту ячеек. Я полагаю, что как только проблема с высотой будет решена, я воспользуюсь noteHeightOfRowsWithIndexesChanged
методом -, чтобы сообщить представлению таблицы, что высота изменилась ... но он все равно будет запрашивать у делегата высоту ... отсюда и мои затруднения.
Заранее спасибо!
источник
Ответы:
Это проблема курицы и яйца. Таблица должна знать высоту строки, потому что она определяет, где будет располагаться данное представление. Но вы хотите, чтобы представление уже было, чтобы вы могли использовать его для определения высоты строки. Итак, что на первом месте?
Ответ заключается в том, чтобы оставить дополнительный
NSTableCellView
(или любой другой вид, который вы используете в качестве «представления ячеек») только для измерения высоты представления. ВtableView:heightOfRow:
методе делегата, доступ к вашей модели для «строки» и установитеobjectValue
наNSTableCellView
. Затем установите ширину представления равной ширине вашей таблицы и (как бы вы это ни хотели) вычислите требуемую высоту для этого представления. Верните это значение.Не вызывайте
noteHeightOfRowsWithIndexesChanged:
from в методе делегатаtableView:heightOfRow:
илиviewForTableColumn:row:
! Это плохо и вызовет мега-проблемы.Чтобы динамически обновлять высоту, вам следует отреагировать на изменение текста (через цель / действие) и пересчитать вычисленную высоту этого представления. Теперь не изменяйте динамически
NSTableCellView
высоту (или любое другое представление, которое вы используете в качестве «представления ячейки»). Таблица должна управлять фреймом этого представления, и вы будете бороться с табличным представлением, если попытаетесь его установить. Вместо этого в вашей цели / действии для текстового поля, где вы вычислили высоту, вызовитеnoteHeightOfRowsWithIndexesChanged:
, что позволит таблице изменить размер этой отдельной строки. Предполагая, что у вас есть настройка маски с автоматическим изменением размера прямо на подпредставлениях (то есть: на подвидахNSTableCellView
), все должно измениться в размере! Если нет, сначала поработайте над маской изменения размера вложенных представлений, чтобы все было правильно с переменной высотой строк.Не забывайте, что
noteHeightOfRowsWithIndexesChanged:
по умолчанию анимация. Чтобы не было анимации:[NSAnimationContext beginGrouping]; [[NSAnimationContext currentContext] setDuration:0]; [tableView noteHeightOfRowsWithIndexesChanged:indexSet]; [NSAnimationContext endGrouping];
PS: Я отвечаю больше на вопросы, размещенные на форумах Apple Dev, чем на переполнение стека.
PSS: я написал NSTableView на основе представления
источник
tableView:heightOfRow:
, я настроил свое представление резервной ячейки со значениями строки (у меня только один столбец) и вернулсяcellView.fittingSize.height
.fittingSize
- это метод NSView, который вычисляет минимальный размер представления, удовлетворяющий его ограничениям.Это стало намного проще в macOS 10.13 с расширением
.usesAutomaticRowHeights
. Подробности здесь: https://developer.apple.com/library/content/releasenotes/AppKit/RN-AppKit/#10_13 (в разделе под названием «NSTableView Automatic Row Heights»).В основном вы просто выбираете
NSTableView
илиNSOutlineView
в редакторе раскадровки и выбираете эту опцию в инспекторе размеров:Затем вы устанавливаете в своем материале
NSTableCellView
верхние и нижние ограничения для ячейки, и ваша ячейка автоматически изменится по размеру. Код не требуется!Ваше приложение будет игнорировать любые высоты, указанные в
heightOfRow
(NSTableView
) иheightOfRowByItem
(NSOutlineView
). Вы можете увидеть, какие высоты рассчитываются для строк вашего автоматического макета, с помощью этого метода:func outlineView(_ outlineView: NSOutlineView, didAdd rowView: NSTableRowView, forRow row: Int) { print(rowView.fittingSize.height) }
источник
TableCellView
редактировать. Не имеет значения, проверено в Атрибутах [Поведение: редактируется] или Инспекторе привязок [Условно установлено редактируемое: Да]Основываясь на ответе Корбина (кстати, спасибо, что пролило свет на это):
Swift 3, NSTableView на основе представления с автоматической компоновкой для macOS 10.11 (и выше)
Моя настройка: у меня есть файл,
NSTableCellView
который разложен с помощью Auto-Layout. Он содержит (помимо других элементов) многострочную строку,NSTextField
которая может иметь до 2 строк. Следовательно, высота всего представления ячейки зависит от высоты этого текстового поля.Я обновляю, чтобы вид таблицы обновлял высоту в двух случаях:
1) При изменении размера представления таблицы:
func tableViewColumnDidResize(_ notification: Notification) { let allIndexes = IndexSet(integersIn: 0..<tableView.numberOfRows) tableView.noteHeightOfRows(withIndexesChanged: allIndexes) }
2) При изменении объекта модели данных:
tableView.noteHeightOfRows(withIndexesChanged: changedIndexes)
Это заставит представление таблицы запросить у делегата новую высоту строки.
func tableView(_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat { // Get data object for this row let entity = dataChangesController.entities[row] // Receive the appropriate cell identifier for your model object let cellViewIdentifier = tableCellViewIdentifier(for: entity) // We use an implicitly unwrapped optional to crash if we can't create a new cell view var cellView: NSTableCellView! // Check if we already have a cell view for this identifier if let savedView = savedTableCellViews[cellViewIdentifier] { cellView = savedView } // If not, create and cache one else if let view = tableView.make(withIdentifier: cellViewIdentifier, owner: nil) as? NSTableCellView { savedTableCellViews[cellViewIdentifier] = view cellView = view } // Set data object if let entityHandler = cellView as? DataEntityHandler { entityHandler.update(with: entity) } // Layout cellView.bounds.size.width = tableView.bounds.size.width cellView.needsLayout = true cellView.layoutSubtreeIfNeeded() let height = cellView.fittingSize.height // Make sure we return at least the table view height return height > tableView.rowHeight ? height : tableView.rowHeight }
Во-первых, нам нужно получить объект нашей модели для row (
entity
) и соответствующий идентификатор представления ячейки. Затем мы проверяем, создали ли мы уже представление для этого идентификатора. Для этого мы должны вести список с представлениями ячеек для каждого идентификатора:// We need to keep one cell view (per identifier) around fileprivate var savedTableCellViews = [String : NSTableCellView]()
Если ничего не сохранено, нам нужно создать (и кэшировать) новый. Мы обновляем представление ячейки с помощью нашего объекта модели и приказываем ему изменить макет всего на основе текущей ширины представления таблицы. Затем
fittingSize
высоту можно использовать как новую высоту.источник
cellView.bounds.size.width = tableView.bounds.size.width
рекомендуется сбросить высоту на меньшее значение. Если вы планируете повторно использовать этот фиктивный вид, установка меньшего числа приведет к "сбросу" подходящего размера.Для тех, кто хочет больше кода, вот полное решение, которое я использовал. Спасибо, Корбин Данн, за то, что указал мне в правильном направлении.
Мне нужно было установить высоту в основном в зависимости от того, насколько высока
NSTextView
у меняNSTableViewCell
.В моем подклассе
NSViewController
я временно создаю новую ячейку, вызываяoutlineView:viewForTableColumn:item:
- (CGFloat)outlineView:(NSOutlineView *)outlineView heightOfRowByItem:(id)item { NSTableColumn *tabCol = [[outlineView tableColumns] objectAtIndex:0]; IBAnnotationTableViewCell *tableViewCell = (IBAnnotationTableViewCell*)[self outlineView:outlineView viewForTableColumn:tabCol item:item]; float height = [tableViewCell getHeightOfCell]; return height; } - (NSView *)outlineView:(NSOutlineView *)outlineView viewForTableColumn:(NSTableColumn *)tableColumn item:(id)item { IBAnnotationTableViewCell *tableViewCell = [outlineView makeViewWithIdentifier:@"AnnotationTableViewCell" owner:self]; PDFAnnotation *annotation = (PDFAnnotation *)item; [tableViewCell setupWithPDFAnnotation:annotation]; return tableViewCell; }
В моем
IBAnnotationTableViewCell
контроллере для моей ячейки (подклассаNSTableCellView
) у меня есть метод настройки-(void)setupWithPDFAnnotation:(PDFAnnotation*)annotation;
который настраивает все выходы и устанавливает текст из моих PDFAnnotations. Теперь я могу «легко» вычислить высоту, используя:
-(float)getHeightOfCell { return [self getHeightOfContentTextView] + 60; } -(float)getHeightOfContentTextView { NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys:[self.contentTextView font],NSFontAttributeName,nil]; NSAttributedString *attributedString = [[NSAttributedString alloc] initWithString:[self.contentTextView string] attributes:attributes]; CGFloat height = [self heightForWidth: [self.contentTextView frame].size.width forString:attributedString]; return height; }
.
- (NSSize)sizeForWidth:(float)width height:(float)height forString:(NSAttributedString*)string { NSInteger gNSStringGeometricsTypesetterBehavior = NSTypesetterLatestBehavior ; NSSize answer = NSZeroSize ; if ([string length] > 0) { // Checking for empty string is necessary since Layout Manager will give the nominal // height of one line if length is 0. Our API specifies 0.0 for an empty string. NSSize size = NSMakeSize(width, height) ; NSTextContainer *textContainer = [[NSTextContainer alloc] initWithContainerSize:size] ; NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:string] ; NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init] ; [layoutManager addTextContainer:textContainer] ; [textStorage addLayoutManager:layoutManager] ; [layoutManager setHyphenationFactor:0.0] ; if (gNSStringGeometricsTypesetterBehavior != NSTypesetterLatestBehavior) { [layoutManager setTypesetterBehavior:gNSStringGeometricsTypesetterBehavior] ; } // NSLayoutManager is lazy, so we need the following kludge to force layout: [layoutManager glyphRangeForTextContainer:textContainer] ; answer = [layoutManager usedRectForTextContainer:textContainer].size ; // Adjust if there is extra height for the cursor NSSize extraLineSize = [layoutManager extraLineFragmentRect].size ; if (extraLineSize.height > 0) { answer.height -= extraLineSize.height ; } // In case we changed it above, set typesetterBehavior back // to the default value. gNSStringGeometricsTypesetterBehavior = NSTypesetterLatestBehavior ; } return answer ; }
.
- (float)heightForWidth:(float)width forString:(NSAttributedString*)string { return [self sizeForWidth:width height:FLT_MAX forString:string].height ; }
источник
Я довольно долго искал решение и пришел к следующему, который отлично работает в моем случае:
- (double)tableView:(NSTableView *)tableView heightOfRow:(long)row { if (tableView == self.tableViewTodo) { CKRecord *record = [self.arrayTodoItemsFiltered objectAtIndex:row]; NSString *text = record[@"title"]; double someWidth = self.tableViewTodo.frame.size.width; NSFont *font = [NSFont fontWithName:@"Palatino-Roman" size:13.0]; NSDictionary *attrsDictionary = [NSDictionary dictionaryWithObject:font forKey:NSFontAttributeName]; NSAttributedString *attrString = [[NSAttributedString alloc] initWithString:text attributes:attrsDictionary]; NSRect frame = NSMakeRect(0, 0, someWidth, MAXFLOAT); NSTextView *tv = [[NSTextView alloc] initWithFrame:frame]; [[tv textStorage] setAttributedString:attrString]; [tv setHorizontallyResizable:NO]; [tv sizeToFit]; double height = tv.frame.size.height + 20; return height; } else { return 18; } }
источник
Поскольку я использую обычай,
NSTableCellView
и у меня есть доступ кNSTextField
моему решению, я добавил методNSTextField
.@implementation NSTextField (IDDAppKit) - (CGFloat)heightForWidth:(CGFloat)width { CGSize size = NSMakeSize(width, 0); NSFont* font = self.font; NSDictionary* attributesDictionary = [NSDictionary dictionaryWithObject:font forKey:NSFontAttributeName]; NSRect bounds = [self.stringValue boundingRectWithSize:size options:NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading attributes:attributesDictionary]; return bounds.size.height; } @end
источник
Вы видели RowResizableViews ? Он довольно старый, и я не тестировал его, но, тем не менее, он может работать.
источник
Вот что я сделал, чтобы это исправить:
Источник: посмотрите документацию XCode в разделе «Высота строки nstableview». Вы найдете образец исходного кода под названием «TableViewVariableRowHeights / TableViewVariableRowHeightsAppDelegate.m».
(Примечание: я смотрю на столбец 1 в виде таблицы, вам придется настроить, чтобы искать в другом месте)
в Delegate.h
IBOutlet NSTableView *ideaTableView;
в Delegate.m
табличное представление делегирует управление высотой строки
- (CGFloat)tableView:(NSTableView *)tableView heightOfRow:(NSInteger)row { // Grab the fully prepared cell with our content filled in. Note that in IB the cell's Layout is set to Wraps. NSCell *cell = [ideaTableView preparedCellAtColumn:1 row:row]; // See how tall it naturally would want to be if given a restricted with, but unbound height CGFloat theWidth = [[[ideaTableView tableColumns] objectAtIndex:1] width]; NSRect constrainedBounds = NSMakeRect(0, 0, theWidth, CGFLOAT_MAX); NSSize naturalSize = [cell cellSizeForBounds:constrainedBounds]; // compute and return row height CGFloat result; // Make sure we have a minimum height -- use the table's set height as the minimum. if (naturalSize.height > [ideaTableView rowHeight]) { result = naturalSize.height; } else { result = [ideaTableView rowHeight]; } return result; }
вам также нужно это, чтобы изменить высоту новой строки (делегированный метод)
- (void)controlTextDidEndEditing:(NSNotification *)aNotification { [ideaTableView reloadData]; }
Надеюсь, это поможет.
Последнее замечание: это не поддерживает изменение ширины столбца.
источник
-[NSTableView preparedCellAtColumn:row]
Вызов кода , который, как сказано в документации, «доступен только для табличных представлений на основе NSCell». При использовании этого метода в таблице на основе представления журнал выводится во время выполнения: «Ошибка NSTableView на основе представления: была вызвана подготовленнаяCellAtColumn: row:. Пожалуйста, зарегистрируйте ошибку с обратной трассировкой из этого журнала или прекратите использование метода».Вот решение, основанное на ответе JanApotheker, измененном, поскольку оно
cellView.fittingSize.height
не возвращало для меня правильную высоту. В моем случае я использую стандартNSTableCellView
,NSAttributedString
для текста textField ячейки и таблицу с одним столбцом с ограничениями для textField ячейки, установленными в IB.В моем контроллере представления я заявляю:
var tableViewCellForSizing: NSTableCellView?
В viewDidLoad ():
tableViewCellForSizing = tableView.make(withIdentifier: "My Identifier", owner: self) as? NSTableCellView
Наконец, для метода делегата tableView:
func tableView(_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat { guard let tableCellView = tableViewCellForSizing else { return minimumCellHeight } tableCellView.textField?.attributedStringValue = attributedString[row] if let height = tableCellView.textField?.fittingSize.height, height > 0 { return height } return minimumCellHeight }
mimimumCellHeight
- константа, равная 30, для резервного копирования, но на самом деле никогда не использовалась.attributedStrings
это мой модельный массивNSAttributedString
.Это отлично подходит для моих нужд. Спасибо за все предыдущие ответы, которые указали мне правильное направление решения этой досадной проблемы.
источник
Это очень похоже на то, что мне приходилось делать раньше. Хотел бы я сказать вам, что я придумал простое и элегантное решение, но, увы, я этого не сделал. Но не из-за отсутствия попыток. Как вы уже заметили, необходимость UITableView знать высоту до построения ячеек действительно заставляет все это казаться довольно круглым.
Моим лучшим решением было передать логику в ячейку, потому что, по крайней мере, я мог изолировать, какой класс нужен, чтобы понимать, как расположены ячейки. Метод вроде
+ (CGFloat) heightForStory:(Story*) story
сможет определить, какой высоты должна быть камера. Конечно, это включало измерение текста и т. Д. В некоторых случаях я изобретал способы кэширования информации, полученной в ходе этого метода, которую затем можно было использовать при создании ячейки. Это было лучшее, что я придумал. Это раздражающая проблема, поскольку, похоже, должен быть лучший ответ.
источник