基于视图的NSTableView,其行具有动态高度

Jiv*_*Voe 82 macos cocoa objective-c osx-lion

我有一个基于视图的应用程序NSTableView.在这个表视图中,我有一些行,这些行的单元格内容包含一个NSTextField启用了自动换行的多行.根据文本内容,NSTextField显示单元格所需的行大小会有所不同.

我知道我可以实现该NSTableViewDelegate方法 - tableView:heightOfRow:返回高度,但高度将根据上面使用的自动换行确定NSTextField.这个词的包装NSTextField同样基于NSTextField它的宽度......这是由宽度决定的NSTableView.

Soooo ......我想我的问题是......对于这个有什么好的设计模式?似乎我尝试的一切都变成了一个令人费解的混乱.因为TableView需要知道细胞的高度来展示它们......并且NSTextField需要了解它的布局以确定自动换行...并且细胞需要知道自动换行来确定它的高度......这是一个圆形的混乱......它让我疯了.

建议?

如果重要,最终结果也将具有可编辑的NSTextFields大小,以调整其中的文本.我已经在视图级别上工作了,但是tableview还没有调整单元格的高度.我想一旦我得到了高度问题,我将使用 - noteHeightOfRowsWithIndexesChanged方法通知表格视图高度发生了变化......但是它仍然会向代表询问高度......因此,我的quandry.

提前致谢!

cor*_*unn 128

这是鸡和蛋的问题.该表需要知道行高,因为它决定了给定视图的位置.但是你想要一个视图已经存在,所以你可以用它来计算行高.那么,哪个先来?

答案是NSTableCellView为了测量视图的高度而保留额外的(或者您正在使用的任何视图作为"单元视图").在tableView:heightOfRow:委托方法中,访问"row"的模型并将其设置为objectValueon NSTableCellView.然后将视图的宽度设置为表格的宽度,然后(但是你想要这样做)计算出该视图所需的高度.返回那个值.

不要noteHeightOfRowsWithIndexesChanged:在委托方法中调用tableView:heightOfRow:或者viewForTableColumn:row:!这很糟糕,会造成巨大的麻烦.

要动态更新高度,那么您应该做的是响应文本更改(通过目标/操作)并重新计算该视图的计算高度.现在,不要动态更改NSTableCellView高度(或者您正在使用的任何视图作为"单元格视图").该表必须控制该视图的框架,如果您尝试设置它,您将对抗tableview.相反,在计算高度的文本字段的目标/操作中,调用 noteHeightOfRowsWithIndexesChanged:,这将使表调整该行的大小.假设您在子视图(即:子视图NSTableCellView)上设置了自动调整遮罩设置,那么事情应该调整好!如果没有,首先处理子视图的大小调整掩码,以使变量行高度正确.

noteHeightOfRowsWithIndexesChanged:默认情况下不要忘记动画.为了使它不具有动画效果:

[NSAnimationContext beginGrouping];
[[NSAnimationContext currentContext] setDuration:0];
[tableView noteHeightOfRowsWithIndexesChanged:indexSet];
[NSAnimationContext endGrouping];
Run Code Online (Sandbox Code Playgroud)

PS:我对Apple Dev Forums上发布的问题的反应比堆栈溢出更多.

PSS:我写了基于NSTableView的视图

  • @ZS:我正在使用自动布局并实现了Corbin的答案.在`tableView:heightOfRow:`中,我使用行的值设置备用单元格视图(我只有一列),并返回`cellView.fittingSize.height`.`fittingSize`是一个NSView方法,它计算满足其约束的视图的最小大小. (3认同)

Cli*_*rum 29

这得到了很多容易的MacOS 10.13.usesAutomaticRowHeights.详细信息如下:https://developer.apple.com/library/content/releasenotes/AppKit/RN-AppKit/#10_13(在标题为"NSTableView自动行高"的部分中).

基本上你只需选择你的NSTableViewNSOutlineView在故事板编辑器中并在尺寸检查器中选择此选项:

在此输入图像描述

然后,您将设置的内容设置为NSTableCellView单元格的顶部和底部约束,并且您的单元格将调整大小以自动适应.无需代码!

您的应用将忽略heightOfRow(NSTableView)和heightOfRowByItem(NSOutlineView)中指定的任何高度.您可以使用以下方法查看为自动布局行计算的高度:

func outlineView(_ outlineView: NSOutlineView, didAdd rowView: NSTableRowView, forRow row: Int) {
  print(rowView.fittingSize.height)
}
Run Code Online (Sandbox Code Playgroud)

  • 遗憾的是,它似乎不适用于包装的文本单元格 (2认同)
  • 经过几天的弄清楚为什么它对我不起作用后,我发现:它仅在“TableCellView”不可编辑时才起作用。在属性[行为:可编辑]或绑定检查器[有条件设置可编辑:是]中检查并不重要 (2认同)

Jan*_*ker 8

根据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)
}
Run Code Online (Sandbox Code Playgroud)

2)当数据模型对象发生变化时:

tableView.noteHeightOfRows(withIndexesChanged: changedIndexes)
Run Code Online (Sandbox Code Playgroud)

这将导致表视图询问它的委托是否有新的行高.

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
}
Run Code Online (Sandbox Code Playgroud)

首先,我们需要获取row(entity)的模型对象和相应的单元视图标识符.然后,我们检查是否已经为此标识符创建了一个视图.为此,我们必须为每个标识符维护一个包含单元格视图的列表:

// We need to keep one cell view (per identifier) around
fileprivate var savedTableCellViews = [String : NSTableCellView]()
Run Code Online (Sandbox Code Playgroud)

如果没有保存,我们需要创建(并缓存)一个新的.我们使用模型对象更新单元格视图,并告诉它根据当前表格视图宽度重新布局所有内容.fittingSize然后可以将高度用作新高度.


Sun*_*kas 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;
}
Run Code Online (Sandbox Code Playgroud)

在我的IBAnnotationTableViewCell哪个是我的单元格的控制器(子类NSTableCellView)我有一个设置方法

-(void)setupWithPDFAnnotation:(PDFAnnotation*)annotation;
Run Code Online (Sandbox Code Playgroud)

它设置所有出口并设置我的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;
}
Run Code Online (Sandbox Code Playgroud)

.

- (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 ;
}
Run Code Online (Sandbox Code Playgroud)

.

- (float)heightForWidth:(float)width forString:(NSAttributedString*)string
{
    return [self sizeForWidth:width height:FLT_MAX forString:string].height ;
}
Run Code Online (Sandbox Code Playgroud)