0

In order to display views in NSTableView I have to calculate of text in NSTextField. I'm using this method:

float heightForStringDrawing(NSString *myString, NSFont *myFont,float myWidth) {
    NSTextStorage *textStorage = [[NSTextStorage alloc] initWithString:myString];
    NSTextContainer *textContainer = [[NSTextContainer alloc] initWithContainerSize:NSMakeSize(myWidth, FLT_MAX)];
    NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init];
    [layoutManager addTextContainer:textContainer];
    [textStorage addLayoutManager:layoutManager];
    [textStorage addAttribute:NSFontAttributeName value:myFont
                        range:NSMakeRange(0, [textStorage length])];


    (void) [layoutManager glyphRangeForTextContainer:textContainer];
    return [layoutManager usedRectForTextContainer:textContainer].size.height;
}

In case of line liners it works good, but when I have more then one line for the exact width of my view height is too small. I've been looking for answer for hours but I still don't have anything. Here's the screen shot:

enter image description here

I'm adding ten to the result of the function because of the padding. As you can see for short message, it works good, for longer it's cropped. Thank you for your help.

Ken Thomases
  • 88,520
  • 7
  • 116
  • 154
PawelPawel
  • 77
  • 1
  • 10

2 Answers2

1

You didn't show the code which calls your heightForStringDrawing() function. What is the width that you're providing? If it's the width of the text field, then that's wrong, because the text within the text field isn't allowed to go all the way to the edges of the text field. There's horizontal padding, too.

Anyway, there's a more straightforward approach. Create a text field matching the properties of the one you're trying to measure. Set its stringValue and font. Set its preferredMaxLayoutWidth to the available width. (Note this is in terms of the alignment rect, not the frame rect. Use -alignmentRectForFrame: if you need to convert.) Now, ask it for its intrinsicSize. You then have to convert that from alignment rect to frame rect using -frameForAlignmentRect:.

Depending on your situation, you can duplicate an existing text field by archiving and unarchiving it, or you can load the NIB which contains it to obtain it.


One way to obtain a text field with the necessary characteristics is to clone an existing one:

NSData* data = [NSKeyedArchiver archivedDataWithRootObject:_label];
NSTextField* clone = [NSKeyedUnarchiver unarchiveObjectWithData:data];

To calculate the needed frame height to fit in a given width:

NSRect rect = { NSZeroPoint, NSMakeSize(desiredWidth, 100 /* arbitrary */) };
rect = [clone alignmentRectForFrame:rect];
clone.preferredMaxLayoutWidth = NSWidth(rect);
rect.size = clone.intrinsicContentSize;
rect = [clone frameForAlignmentRect:rect];
CGFloat resultingHeight = NSHeight(rect);

Now, in you screenshot, the row height has to be taller than the text field height because there's a margin there. How much taller depends on your layout. Since you mention constraints in your comment, you're presumably using auto layout. In that case, the margins (constraint distances) would actually be applied to the alignment rect of the text field. So, you would skip the second to last line of code. That is, the row height would be the text field's intrinsic height (not adjusted) plus the margins imposed by your constraints.

To avoid a lot of this stuff with fiddling with margins, you should perhaps work on a complete view hierarchy matching your table cell view. So, you'd load your table cell view NIB directly. You'd set the text field's content as appropriate (and whatever other content your cell view's subviews require). You'd apply a width constraint to the cell view itself to simulate the column width. Then, you'd ask the cell view for its fittingSize. Let the auto layout system compute the overall height, including margins and everything implied by your constraints, from the content.

Of course, this only works if the text field is using preferredMaxLayoutWidth. Otherwise, it won't increase its intrinsic height in response to wrapping. Since you don't want to hard-code the preferred width or margin sizes, you can do two passes. After setting the content for the controls within the cell view and adding the width constraint, call -layoutSubtreeIfNeeded. Then, query the text field's frame, convert it to an alignment rect, set the preferredMaxLayoutWidth to the alignment width, and only then query the cell view's fittingSize.

Ken Thomases
  • 88,520
  • 7
  • 116
  • 154
  • `NSString *s = @"too long string value to put it in comment";` `NSFont *font = [NSFont fontWithName:@"Helvetica Neue" size:13];` `h = heightForStringDrawing(s,font, 100-4);` This is how I invoke this method. 100 - width of the rounded green customview. -4 because of the constraints customView - textfield (2 both sides). I've already tried your approach but it's doesn't work too. It returns even lower height then required. – PawelPawel Aug 23 '15 at 20:47
  • I updated my answer. I had forgotten to note that you have to convert the intrinsic height from alignment rect to the frame rect. – Ken Thomases Aug 23 '15 at 21:31
  • @Ąowski, I updated my answer again with example code. I also describe an approach using a whole cell view, since it's error prone to reproduce your constraint margins in code. – Ken Thomases Aug 24 '15 at 01:03
0

Perfectly working solution for me:

// creating temporary cell just to take height from it later
NSTextFieldCell *tmp = [NSTextFieldCell new];
// settings proper values from out NSTextField which we want to check height for
[tmp setStringValue:s];
[tmp setFont:font];
NSRect rect;
rect.size.height=FLT_MAX;
// width which we want to check height for
// in my case 100 (width of nstextfield), -4 (minus left and right margin)
rect.size.width=100-4; 
// getting needed height
h = [tmp cellSizeForBounds:rect].height;
PawelPawel
  • 77
  • 1
  • 10
  • This gets the cell size right, but not the size of the text field that would hold it. It ignores some margins around the boundary that the text field uses. – Ken Thomases Aug 23 '15 at 23:33
  • What about putting it into `NSTextField` in some way and then taking the size? I tried what you posted above but either it doesn't work or I don't get it. – PawelPawel Aug 23 '15 at 23:52