View-based NSTableView view makes row height dependent on content

I need a little help with NSTableView and dynamic row height

What I have:

A view-based single column NSTableVIew is associated with an array controller. Each NSTableCellView contains three subheadings: NSImageView, NSTextField (single line) and NSTextField (multi-line). Basically, this is the chat interface, so you will have a list of messages, senders and avatars.

What I want to achieve:

When the text is longer than the minimum line height, the line expands to fit the content. Like iMessage, bubbles expand to fit the message.

... which seems very natural, but of all the relevant solutions that I found on the Internet ( Ref 1 , Ref 2 ), none of which work for me.

Ref 2 looks fantastically written, but none of this applies to my application, as the sample project uses a third-party automatic code layout, and all this is for iOS. Ref 1 gives a very promising solution written in English. I tried to configure it using a "dummy view" as described in the solution, but could not correctly change and measure the height.

Here is my code for tableView:heightOfRow: _samplingView is a dummy view that has working limitations and is identical to that in tableView .

 - (CGFloat)tableView:(NSTableView *)tableView heightOfRow:(NSInteger)row { NSTextField *textField; NSTextFieldCell *messageCell; for (NSView *subview in [_samplingView subviews]) { if ([[subview identifier] isEqualToString:@"message"]) { textField = (NSTextField*)subview; messageCell = ((NSTextField*)subview).cell; } } Message *message = [[_messagesArrayController arrangedObjects] objectAtIndex:row]; _samplingView.objectValue = message; CGFloat width = [[[tableView tableColumns] objectAtIndex:1] width]; [_samplingView setBounds:NSMakeRect(0, 0, width, CGFLOAT_MAX)]; [_samplingView display]; CGFloat optimalHeight = 10 + [messageCell cellSize].height; //messageCell size stays the same when I change samplingView to try to measure the height return optimalHeight; } 

Result: all row heights remain unchanged, so when I change the width of the _samplingView , the edge size does not change messageCell . I thought auto-layout would take care of this compression / expansion and let me measure the height. In fact, I am very confused.

Edit: for reference, it looks like this:

  +-----------+---------------------------------------------------+ | | NSTextField | |NSImageView| sender | | avatar +---------------------------------------------------+ | | | | | NSTextField (multiline) | +-----------| message | | | | | | (high compression/hugging priority) | | | (this view should decide the height of row) | | | | +-----------+---------------------------------------------------+ 
+7
objective-c cocoa interface-builder xcode5 nstableview
source share
2 answers

First, you should not set the boundaries of a fictitious representation. If you have not used the automatic layout, you must set the frame.

Given that you are using automatic layout, you should not do this either. You should temporarily add a constraint on the fictitious view to limit its width.

Then do not call -display . Call -layoutSubtreeIfNeeded and then query the dummy view of fittingSize . The height of the string should be the height of fittingSize . I'm not sure why you are trying to set it at the height of messageCell plus some kind of arbitrary magic number. There is no need to access the text field or its cell.

You say that you have limitations for the proper functioning of the cell. However, you will need to make sure that you have constraints that make the cell height big enough to hold subviews. This can be a problem, since the table view will control the height of the real (non-fictitious) cell view, and if the table view temporarily makes it too short to hold subqueries with the required interval, you will get unsatisfactory error limits. Thus, the solution is to reduce the priority of the vertical constraint at the bottom (or height) of the cell view so that it is below 1000 (required). It may be 250 (low). This should be greater than 50, i.e. NSLayoutPriorityFittingSizeCompression . That the priority of the constraints that fittingSize temporarily uses is to try to make the representation as small as possible.

This should get a height suitable for the original content.

As your Ref 1 (and stevesilva) notes, you need to somehow observe the changes in the contents of a multi-line text field of variable height. When it changes, you will need to tell the table view that the row height (can be) changed by calling -noteHeightOfRowsWithIndexesChanged: on it.

Finally, you should strive to make -tableView:heightOfRow: as efficient as possible. Therefore, I suggest that you calculate all the heights of the lines on the first request and cache them. -tableView:heightOfRow: should just return a value from the cache. Therefore, when you notice that the message has changed, you must calculate the new height for this line and compare it with the cache height. If and only if the height has actually changed - not all changes in the content will change the height - save the new value in the cache and tell the table that the height has changed.

+1
source share

You need to call the tableView method noteHeightOfRowsWithIndexesChanged noteHeightOfRowsWithIndexesChanged: when changing the selection width. This method is mentioned in your "Link 1." As stated in the documentation for the method:

If the delegate implements tableView: heightOfRow: this method immediately redefines the table view using the height of the row that the delegate provides.

For NSView-based tables, this method will animate. To disable animation, create an NSAnimationContext group and set the duration to 0. Then call this method and end the grouping.

In the table view, the row height is cached. Conceptually, when you want to change the height of a line, you must mark the height as dirty in order to get the delegation method that you implemented that is re-called for the lines you specify. There is nothing like cocoa binding using KVO to view changes in all the key steps that affect rowHeight, at least not by default.

0
source share

All Articles