1

I am attempting to measure an NSAttributedString's height given a constant width using the following method:

-(CGFloat)calculateHeightForAttributedString:(NSAttributedString*)attributedNotes {
    CGFloat scrollerWidth = [NSScroller scrollerWidthForControlSize:NSRegularControlSize scrollerStyle:NSScrollerStyleLegacy];
    CGFloat width = self.tableView.frame.size.width - self.cellNotesWidthConstraint - scrollerWidth;
    // http://www.cocoabuilder.com/archive/cocoa/54083-height-of-string-with-fixed-width-and-given-font.html
    NSTextView *tv = [[NSTextView alloc] initWithFrame:NSMakeRect(0, 0, width - 20, 1e7)];
    tv.font = [NSFont userFontOfSize:32];
    [tv.textStorage setAttributedString:attributedNotes];
    [self setScaleFactor:[self convertSliderValueToFontScale:self.fontScaleSlider] forTextView:tv];

    [tv.layoutManager glyphRangeForTextContainer:tv.textContainer];
    [tv.layoutManager ensureLayoutForTextContainer:tv.textContainer];

    return [tv.layoutManager usedRectForTextContainer:tv.textContainer].size.height + 10.0f; // add a little bit of a buffer
}

Basically, the width is the table view's size minus the scroller and a little bit of each cell that is used to display other information. This method works really well as long as the text scale (via convertSliderValueToFontScale:) is 1.0. The result from usedRectForTextContainer is incorrect if I change the scale factor, however -- as if the scale factor was not being accounted for.

The scale is set in setScaleFactor:forTextView: on the NSTextView as follows (scalar is the actual scale amount):

[textView scaleUnitSquareToSize:NSMakeSize(scaler, scaler)];

Any ideas on how to fix this?

Edit: I've got a sample project to try here: Github. Strangely enough, things work if the scale is < 0, and they randomly seem to work in the 4.XXX range on occasion...

Deadpikle
  • 356
  • 6
  • 22
  • As I see it, `scaleUnitSquareToSize` changes the size of the points, the number of points in local coordinates stays the same. – Willeke Aug 23 '16 at 00:56
  • I would imagine that NSLayoutManager/NSTextContainer know **something** about `scaleUnitSquareToSize` given the fact that text wrapping is adjusted as the text is scaled up (assuming your NSScrollView has horizontal scrolling disabled). – Deadpikle Aug 24 '16 at 12:52
  • No dice. I've got a sample project up here: https://github.com/Deadpikle/CocoaStringHeightWithScale . Strangely enough, there ARE some values that work -- but it seems to differ each run of the software (although it is always ~4). It also seems to work if scale < 0. Thanks for your suggestions thus far. – Deadpikle Aug 25 '16 at 12:55

1 Answers1

0

The answer turns out to be simple: add the NSTextView as a subview of an NSClipView with the same frame as the NSTextView.

The final height function is as follows:

-(CGFloat)calculateHeightForAttributedString:(NSAttributedString*)attributedNotes {
    CGFloat width = self.textView.frame.size.width;
    // http://www.cocoabuilder.com/archive/cocoa/54083-height-of-string-with-fixed-width-and-given-font.html
    NSTextView *tv = [[NSTextView alloc] initWithFrame:NSMakeRect(0, 0, width, 1e7)];
    tv.horizontallyResizable = NO;
    tv.font = [NSFont userFontOfSize:32];
    tv.alignment = NSTextAlignmentLeft;
    [tv.textStorage setAttributedString:attributedNotes];
    [self setScaleFactor:self.slider.floatValue forTextView:tv];

    // In order for usedRectForTextContainer: to be accurate with a scale, you MUST
    // set the NSTextView as a subview of an NSClipView!
    NSClipView *clipView = [[NSClipView alloc] initWithFrame:NSMakeRect(0, 0, width, 1e7)];
    [clipView addSubview:tv];

    [tv.layoutManager glyphRangeForTextContainer:tv.textContainer];
    [tv.layoutManager ensureLayoutForTextContainer:tv.textContainer];
    return [tv.layoutManager usedRectForTextContainer:tv.textContainer].size.height;
}
Deadpikle
  • 356
  • 6
  • 22