動的な高さを持つ行を持つビューベースのNSTableView


84

ビューベースのアプリケーションがありますNSTableView。このテーブルビュー内にはNSTextField、ワードラップが有効になっている複数行で構成されるコンテンツを含むセルを持つ行があります。のテキストコンテンツにNSTextField応じて、セルを表示するために必要な行のサイズは異なります。

高さを返すNSTableViewDelegateメソッドを実装できることはわかっていますtableView:heightOfRow:が、高さはで使用されているワードラップに基づいて決定されますNSTextFieldNSTextField同様に、のワードラップは、の幅に基づいていますNSTextField。これは、の幅によって決まりNSTableViewます。

すっごく…私の質問は…これに適したデザインパターンは何ですか?私が試みることはすべて、複雑な混乱に終わっているようです。TableViewは、セルをレイアウトするためにセルの高さのNSTextField知識を必要とするため...そしてワードラップを決定するためにそのレイアウトの知識を必要とします...そしてセルはその高さを決定するためにワードラップの知識を必要とします...それは円形の混乱です...そしてそれは私を狂気に駆り立ててます。

提案?

重要な場合は、最終結果にも編集可能でNSTextFields、サイズを変更してその中のテキストに合わせて調整します。私はすでにこれをビューレベルで機能させていますが、テーブルビューはまだセルの高さを調整していません。高さの問題が解決したら、-noteHeightOfRowsWithIndexesChangedメソッドを使用してテーブルビューに高さが変更されたことを通知します...しかし、それでもデリゲートに高さを要求します...したがって、私の悩みの種です。

前もって感謝します!


ビューの幅と高さの両方がコンテンツによって変化する可能性がある場合、反復する必要があるため、それらの定義は複雑になります(ただし、キャッシュすることはできます)。しかし、あなたのケースは一定の幅に対して高さが変化しているようです。幅がわかっている場合(つまり、ウィンドウまたはビューの幅に基づいている場合)、ここでは何も複雑ではありません。幅が可変である理由がわかりません。
ロブネイピア2011

内容によって高さのみ変更できます。そして、私はすでにその問題を解決しました。高さを自動的に調整するNSTextFieldサブクラスがあります。問題は、その調整の知識をテーブルビューのデリゲートに戻すことです。これにより、それに応じて高さを更新できます。
Jiva DeVoe 2011

@Jiva:これをまだ解決したかどうか疑問に思います。
Joshua Nozzi 2011年

私は似たようなことをしようとしています。自分の高さを調整するNSTextFieldサブクラスのアイデアが好きです。そのサブクラスに高さ変更の(デリゲート)通知を追加し、適切なクラス(データソース、アウトラインデリゲートなど)でそれを監視して、アウトラインに情報を取得するのはどうですか?
ジョンベルマン2011年

非常に関連性の高い質問で、ここにあるものよりも役立つ回答があります:NSStringsに基づくNSTableView行の高さ
アシュリー

回答:


132

これは鶏が先か卵が先かという問題です。特定のビューがどこにあるかを決定するため、テーブルは行の高さを知る必要があります。ただし、ビューを既に配置して、行の高さを把握できるようにする必要があります。それで、どちらが最初に来るのですか?

答えはNSTableCellView、ビューの高さを測定するためだけに、余分な(または「セルビュー」として使用しているビュー)を保持することです。でtableView:heightOfRow:デリゲートメソッド「行」と設定のためのアクセスお使いのモデルobjectValueNSTableCellView。次に、ビューの幅をテーブルの幅に設定し、(ただし、実行したい場合は)そのビューに必要な高さを計算します。その値を返します。

noteHeightOfRowsWithIndexesChanged:デリゲートメソッドtableView:heightOfRow:またはviewForTableColumn:row:!から呼び出さないでください。それは悪いことであり、大きな問題を引き起こすでしょう。

高さを動的に更新するには、(ターゲット/アクションを介して)テキストの変更に応答し、そのビューの計算された高さを再計算する必要があります。ここで、NSTableCellViewの高さ(または「セルビュー」として使用しているビュー)を動的に変更しないでください。テーブルそのビューのフレームを制御する必要があり、設定しようとするとテーブルビューと戦うことになります。代わりに、高さを計算したテキストフィールドのターゲット/アクションで、を呼び出します noteHeightOfRowsWithIndexesChanged:。これにより、テーブルの個々の行のサイズが変更されます。自動サイズ変更マスクをサブビュー(つまり、のサブビュー)に正しく設定しているとすると、サイズは適切にNSTableCellView変更されます。そうでない場合は、最初にサブビューのサイズ変更マスクを操作して、行の高さが可変になるようにします。

noteHeightOfRowsWithIndexesChanged:デフォルトでアニメーション化することを忘れないでください。アニメートしないようにするには:

[NSAnimationContext beginGrouping];
[[NSAnimationContext currentContext] setDuration:0];
[tableView noteHeightOfRowsWithIndexesChanged:indexSet];
[NSAnimationContext endGrouping];

PS:私はApple Dev Forumsに投稿された質問に対して、スタックオーバーフローよりも多く回答します。

PSS:ビューベースのNSTableViewを作成しました


返信ありがとうございます。もともと見たことがなかったのですが…正解としてマークしました。開発フォーラムは素晴らしいです、私もそれらを使用します。
Jiva DeVoe 2012

「そのビューに必要な高さを計算します。その値を返します。」..それが私の問題です:(。ビューベースのtableViewを使用していますが、その方法がわかりません!
Mazyod 2012年

@corbin答えてくれてありがとう、これは私が私のアプリに持っていたものです。しかし、マーベリックスに関しては問題がありました。あなたは明らかにこのテーマに精通しているので、私の質問をチェックしていただけませんか。ここで:stackoverflow.com/questions/20924141/...
アレックス

1
@corbinにはこれに対する標準的な答えがありますが、NSTableViewsで自動レイアウトと制約を使用していますか?自動レイアウトには潜在的な解決策があります。EstimatedHeightForRowAtIndexPath API(Cocoaにはない)がなくてもそれで十分かどうか疑問に思う
ZS 2014

3
@ZS:Auto Layoutを使用していて、Corbinの回答を実装しています。tableView:heightOfRow:私は列の値を持つ私の予備セルのビューを設定(私は1つの列のみを持っている)、およびリターンcellView.fittingSize.heightfittingSizeは、制約を満たすビューの最小サイズを計算するNSViewメソッドです。
Peter Hosey

43

これは、macOS10.13.usesAutomaticRowHeights。を使用するとはるかに簡単になりました。詳細は次のとおりです:https://developer.apple.com/library/content/releasenotes/AppKit/RN-AppKit/#10_13(「NSTableView自動行の高さ」というタイトルのセクション)。

基本的には、ストーリーボードエディタでNSTableViewまたはNSOutlineViewを選択し、サイズインスペクタで次のオプションを選択するだけです。

ここに画像の説明を入力してください

次にNSTableCellView、セルに上下の制約を設定するように設定すると、セルのサイズが自動的に合わせて変更されます。コードは必要ありません!

アプリはheightOfRowNSTableView)とheightOfRowByItemNSOutlineView)で指定された高さを無視します。この方法を使用して、自動レイアウト行に対してどの高さが計算されているかを確認できます。

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

@tofutim:正しい制約を使用し、動的な高さの各NSTextFieldの[優先幅]設定が[自動]に設定されていることを確認した場合に、ラップされたテキストセルで正しく機能する場合。stackoverflow.com/questions/46890144/…
Ely

@tofutimは正しいと思います。これは、NSTextViewをTableViewに配置すると機能しません。アーカイブしようとしますが、機能しません。ただし、他のコンポーネントでも機能します。例:NSImage ..
アルバート

私は、行の固定の高さを使用するが、それは私のために動作しません
ケンジラに

2
なぜそれが機能しないのかを数日理解した後、私は発見しました:それTableCellViewは編集できない場合にのみ機能します。:またはバインディングインスペクタ[はい条件付設定編集可能]:問題の属性[編集可能な行動]にチェックされていません
ルカシュ

テーブルセルに実際の高さの制約を設定することがいかに重要かを強調したいと思います。私の場合はそうではなかったため、セルの計算されたfittingSizeは0.0であり、フラストレーションが発生しました。
green_knight

9

コービンの答えに基づいて(ところで、これにいくつかの光を当ててくれてありがとう):

Swift 3、macOS 10.11(およびそれ以降)の自動レイアウトを備えたビューベースのNSTableView

私のセットアップ:NSTableCellView自動レイアウトを使用してレイアウトされたがあります。これには、(他の要素に加えて)NSTextField最大2行の複数行が含まれます。したがって、セルビュー全体の高さは、このテキストフィールドの高さに依存します。

テーブルビューに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高さは、新しい高さとして使用することができます。


これは完璧な答えです。複数の列がある場合は、テーブルの幅ではなく、列の幅に境界を設定する必要があります。
Vojto 2017

また、設定するときにcellView.bounds.size.width = tableView.bounds.size.width、高さを低い数値にリセットすることをお勧めします。このダミービューを再利用する場合は、小さい数値に設定すると、フィッティングサイズが「リセット」されます。
Vojto 2017

7

より多くのコードが必要な人のために、これが私が使用した完全なソリューションです。私を正しい方向に向けてくれたcorbindunnに感謝します。

私はどのように高いAに関連して、主に高さを設定するために必要な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 ;
}

heightForWidth:forString:はどこに実装されていますか?
ZS 2014

申し訳ありません。答えに追加しました。これを入手した元のSO投稿を見つけようとしましたが、見つかりませんでした。
Sunkas 2014

1
ありがとう!それは私にとってほとんど機能していますが、それは私に不正確な結果を与えます。私の場合、NSTextContainerにフィードしている幅が間違っているようです。NSTableViewCellのサブビューを定義するために自動レイアウトを使用していますか?「self.contentTextView」の幅はどこから来ていますか?
ZS 2014

私はここに私の問題についての質問を投稿しました:stackoverflow.com/questions/22929441/…–
ZS

3

私はかなり長い間解決策を探していましたが、私の場合はうまくいく次の解決策を思いつきました:

- (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;
    }
}

私の場合、そのラベルに表示するテキストがすべて収まるように、特定の列を垂直方向に拡張したかったので、問題の列を取得して、そこからフォントと幅を取得します。
Klajd Deda 2016

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

ありがとう!あなたは私の救世主です!正しく動作するのはこれだけです。私はこれを理解するために丸一日を費やしました。
トミー

2

RowResizableViewsを見たことがありますか?それはかなり古く、私はそれをテストしていませんが、それでも動作する可能性があります。


2

これを修正するために私が行ったことは次のとおりです。

出典:「行の高さnstableview」の下にあるXCodeのドキュメントを調べてください。「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];
}

これがお役に立てば幸いです。

最後の注意:これは列幅の変更をサポートしていません。


あなたはロック!:) ありがとうございました。
旗手2013

4
残念ながら、Appleのサンプルコード(現在バージョン1.1としてマークされている)は、ビューベースのテーブル(この質問について)ではなく、セルベースのテーブルを使用しています。コードは-[NSTableView preparedCellAtColumn:row]、ドキュメントが「NSCellベースのテーブルビューでのみ使用可能」と述べているを呼び出します。ビューベースのテーブルでこのメソッドを使用すると、実行時に次のログ出力が生成されます:「ビューベースのNSTableViewエラー:preparedCellAtColumn:row:が呼び出されました。このログのバックトレースでバグをログに記録するか、メソッドの使用を停止してください」
Ashley

1

これは、JanApothekerの回答に基づいた解決策でありcellView.fittingSize.height、正しい高さを返さなかったために変更されています。私の場合、標準NSTableCellViewNSAttributedStringセルのtextFieldテキスト用の、、およびIBで設定されたセルのtextField用の制約を持つ単一列テーブルを使用しています。

ビューコントローラで、次のように宣言します。

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

これは私のニーズに完全に対応しています。この厄介な問題に対して正しい方向を示してくれた以前のすべての回答に感謝します。


1
とても良い。1つの問題。テーブルを編集できないか、textfield.fittingSize.heightが機能せず、ゼロを返します。viewdidloadでtable.isEditable = falseを設定します
jiminybob99 2017

0

これは私が以前にやらなければならなかったことによく似ています。シンプルでエレガントな解決策を思いついたと言えればいいのですが、残念ながら、思いつきませんでした。しかし、試みの欠如のためではありません。セルを作成する前にUITableViewが高さを知る必要があることにすでに気付いたように、実際にはすべてがかなり円形に見えます。

私の最善の解決策は、ロジックをセルにプッシュすることでした。少なくとも、セルがどのように配置されているかを理解するために必要なクラスを分離できたからです。次のような方法

+ (CGFloat) heightForStory:(Story*) story

セルの高さを決定することができます。もちろん、テキストの測定なども含まれていました。場合によっては、この方法で取得した情報をキャッシュする方法を考案し、セルの作成時に使用できるようにしました。それは私が思いついた最高でした。より良い答えがあるはずですが、それは腹立たしい問題です。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.