我的应用程序中包含基于视图的NSTableView
。在这个表视图中,我有一些行,其单元格的内容由多行NSTextField
组成,并启用了自动换行。根据{{1}}的文本内容,显示单元格所需的行大小会有所不同。
我知道我可以实施NSTextField
方法 - NSTableViewDelegate
来返回高度,但高度将根据tableView:heightOfRow:
上使用的自动换行确定。 NSTextField
的自动换行同样取决于NSTextField
的宽度......这取决于NSTextField
的宽度。
Soooo ......我想我的问题是......对于这个有什么好的设计模式?似乎我尝试的一切都变成了一个令人费解的混乱。由于TableView需要知道细胞的高度才能将它们排出......并且NSTableView
需要知道它的布局以确定自动换行...并且单元格需要知道自动换行以确定它的高度......这是一个圆形的混乱...它正在驱使我疯狂。
建议?
如果重要,最终结果也会有可编辑的NSTextField
,它会调整大小以适应其中的文字。我已经在视图级别上工作了,但是tableview还没有调整单元格的高度。我想,一旦我解决了高度问题,我将使用 - NSTextFields
方法通知表格视图高度发生变化......但是它仍然会向代表询问高度......因此,我的quandry。
提前致谢!
答案 0 :(得分:127)
这是鸡和蛋的问题。该表需要知道行高,因为它决定了给定视图的位置。但是你想要一个视图已经存在,所以你可以用它来计算行高。那么,哪个先来?
答案是为了测量视图的高度,保留额外的NSTableCellView
(或者您正在使用的任何视图作为“单元格视图”)。在tableView:heightOfRow:
委托方法中,访问“行”模型并在objectValue
上设置NSTableCellView
。然后将视图的宽度设置为表格的宽度,然后(但是你想要这样做)计算出该视图所需的高度。返回那个值。
请勿在委托方法noteHeightOfRowsWithIndexesChanged:
或tableView:heightOfRow:
中致电viewForTableColumn:row:
!这很糟糕,会造成巨大的麻烦。
要动态更新高度,那么您应该做的是响应文本更改(通过目标/操作)并重新计算该视图的计算高度。现在,不要动态更改NSTableCellView
的高度(或您用作“单元格视图”的任何视图)。表必须控制该视图的框架,如果您尝试设置它,您将对抗tableview。相反,在计算高度的文本字段的目标/操作中,调用noteHeightOfRowsWithIndexesChanged:
,这将使表格调整该行的大小。假设您在子视图上设置了自动调整掩码(即:NSTableCellView
的子视图),事情应该调整大小!如果没有,首先处理子视图的大小调整掩码,以使行变高的行高度正确。
默认情况下,不要忘记noteHeightOfRowsWithIndexesChanged:
动画。为了使它不具有动画效果:
[NSAnimationContext beginGrouping];
[[NSAnimationContext currentContext] setDuration:0];
[tableView noteHeightOfRowsWithIndexesChanged:indexSet];
[NSAnimationContext endGrouping];
PS:我对Apple Dev Forums上发布的问题的反应比堆栈溢出更多。
PSS:我写了基于NSTableView的视图
答案 1 :(得分:27)
使用.usesAutomaticRowHeights
在 macOS 10.13 中更容易实现。详细信息如下:https://developer.apple.com/library/content/releasenotes/AppKit/RN-AppKit/#10_13(在标题为“NSTableView自动行高”的部分中)。
基本上,您只需在情节提要编辑器中选择NSTableView
或NSOutlineView
,然后在尺寸检查器中选择此选项:
然后,您将NSTableCellView
中的内容设置为对单元格具有顶部和底部约束,并且您的单元格将调整大小以自动适应。 无需代码!
您的应用会忽略heightOfRow
(NSTableView
)和heightOfRowByItem
(NSOutlineView
)中指定的任何高度。您可以使用以下方法查看为自动布局行计算的高度:
func outlineView(_ outlineView: NSOutlineView, didAdd rowView: NSTableRowView, forRow row: Int) {
print(rowView.fittingSize.height)
}
答案 2 :(得分:7)
对于想要更多代码的人来说,这是我使用的完整解决方案。谢谢corbin dunn指出我正确的方向。
我需要设置的高度主要取决于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 ;
}
答案 3 :(得分:7)
根据Corbin的回答(顺便说一下,对此有所了解):
Swift 3,基于视图的NSTableView,支持macOS 10.11(及更高版本)的自动布局
我的设置:我有一个使用自动布局布局的NSTableCellView
。它包含(除了其他元素之外)多行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
}
首先,我们需要获取行(entity
)的模型对象和相应的单元视图标识符。然后,我们检查是否已经为此标识符创建了一个视图。为此,我们必须为每个标识符维护一个包含单元格视图的列表:
// We need to keep one cell view (per identifier) around
fileprivate var savedTableCellViews = [String : NSTableCellView]()
如果没有保存,我们需要创建(并缓存)一个新的。我们使用模型对象更新单元格视图,并告诉它根据当前表格视图宽度重新布局所有内容。然后可以将fittingSize
高度用作新高度。
答案 4 :(得分:3)
由于我使用自定义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
答案 5 :(得分:2)
你看过RowResizableViews了吗?它已经很老了,我还没有对它进行过测试,但它仍然可以工作。
答案 6 :(得分:2)
以下是我为解决这个问题所做的工作:
来源:查看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];
}
我希望这会有所帮助。
最后注意:这不支持更改列宽。
答案 7 :(得分:1)
这听起来很像以前我必须做的事情。我希望我可以告诉你,我提出了一个简单,优雅的解决方案,但是,唉,我没有。不是因为缺乏尝试。正如您已经注意到UITableView需要知道在构建单元格之前的高度,所以它们看起来都非常圆。
我最好的解决方案是将逻辑推向细胞,因为至少我可以分离出需要理解细胞如何布置的类。像
这样的方法+ (CGFloat) heightForStory:(Story*) story
能够确定细胞的高度。当然,这涉及测量文本等。在某些情况下,我设计了一种方法来缓存在此方法中获得的信息,然后可以在创建单元格时使用。这是我想出的最好的。这是一个令人愤怒的问题,因为似乎应该有一个更好的答案。
答案 8 :(得分:1)
这是一个基于JanApotheker答案的解决方案,修改为cellView.fittingSize.height
没有为我返回正确的高度。在我的情况下,我使用标准NSTableCellView
,NSAttributedString
用于单元格的textField文本,以及单个列表,其中包含在IB中设置的单元格textField的约束。 / p>
在我的视图控制器中,我声明:
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
的模型数组。
这完全符合我的需要。感谢所有以前的答案,这使我指出了这个棘手问题的正确方向。