17

I'm attempting to resize a text field / view automatically depending on its current width. In other words I want the width to stay constant but resize the height according to the text supplied to it.

It seems to be working but for some reason is aligning to the bottom of my window despite efforts to manually move it back. Can anybody see what I'm doing wrong here?

enter image description here

NSString *temp = @"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer vel felis nec massa ultricies blandit non id arcu. Sed enim est, facilisis a feugiat in, consectetur eget arcu. Aenean rutrum, lacus id euismod congue, nisl erat vulputate lectus, non faucibus tortor purus sed sem. Donec tempor dui egestas velit auctor vitae faucibus diam facilisis. Morbi convallis nulla quis est pulvinar quis ultricies sem sollicitudin.";

myText.stringValue = temp;

NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys:
                            [NSFont systemFontOfSize:12], NSFontAttributeName,
                            [NSParagraphStyle defaultParagraphStyle], NSParagraphStyleAttributeName,
                            nil];

NSSize size = NSMakeSize(window.frame.size.width, 200);
myText.frame = [temp boundingRectWithSize:size options:NSLineBreakByWordWrapping | NSStringDrawingUsesLineFragmentOrigin attributes:attributes];

EDIT: Even when manually moving the frame it's clear that the text is still getting cut off. The newly adjusted size is not quite there. enter image description here

this is in reference to: NSString boundingRectWithSize slightly underestimating the correct width - why? and NSTextView or NSTextField automatically resize bounds / frame?

Community
  • 1
  • 1
Desh__
  • 899
  • 1
  • 10
  • 16
  • Maybe change height from 200 to something way larger so that the frame size knows to grow vertically. – rein Aug 23 '12 at 05:48
  • @rein I keep playing with that and while it does infact re-size the new frame its still clipping text on the bottom. The new frame isnt quite large enough. See the updated question for a new screenshot. – Desh__ Aug 23 '12 at 16:28

4 Answers4

25

I had the same Problem. I found the following in the Documentation:

To correctly draw and size multi-line text, pass NSStringDrawingUsesLineFragmentOrigin in the options parameter.

This method returns fractional sizes (in the size component of the returned CGRect); to use a returned size to size views, you must raise its value to the nearest higher integer using the ceil function.

So this solved it for me:

CGRect rect = [myLabel.text boundingRectWithSize:CGSizeMake(myLabel.frame.size.width, CGFLOAT_MAX)
                                                            options:NSStringDrawingUsesLineFragmentOrigin
                                                            attributes:@{NSFontAttributeName: myLabel.font}
                                                            context:nil];
rect.size.width = ceil(rect.size.width);
rect.size.height = ceil(rect.size.height);

Update (Swift 5.1)

An alternative Swift way to do this would be:

let size = myLabel.text!.size(
    withAttributes: [.font: myLabel.font!]
)
let rect = CGSize(
  width: ceil(size.width),
  height: ceil(size.height)
)

See this answer for an Objective-C example.

r-dent
  • 685
  • 8
  • 22
  • 2
    OR'ing with NSLineBreakByWordWrapping is not valid and doesn't work for me anyways. It's not a member of the NSStringDrawingOptions enum. – iphone007 Nov 21 '14 at 21:37
  • Hmm.. You are right! Did you try it with just `NSStringDrawingUsesLineFragmentOrigin`? Did it work? I would edit the snippet then. – r-dent Nov 22 '14 at 22:07
  • Size is usually calculated when you have to add an item underneath the label. So Calculate the size and add item under the label according to height calculated, for label adjustment, do [myLabel sizeToFit]; – AsifHabib Dec 12 '14 at 07:39
  • 1
    Thanks! Universal answer for me is RTFM :) – surfrider Feb 12 '15 at 06:50
  • Manual says to raise the result to the 'nearest higher integer'. I presume they meant ceil(h + 1) because that was how I got it to work. Seriously though, why isn't that correction made in the API? Dammit Apple... – Jonathan Zrake Feb 09 '19 at 05:14
2

It's a bit roundabout, but I've found querying the NSTextFieldCell gives much more accurate/reliable height results than using [NSAttributedString boundingRectWithSize:options:].

Assuming the string of interest is already in an NSTextField with the appropriate formatting/wrapping options set, then query the text cell for what size it thinks it needs to fit in a specified width:

NSTextField* textField = ...

NSSize cellSize = [textField.cell cellSizeForBounds:NSMakeRect(0, 0, pathText.frame.size.width, CGFLOAT_MAX)];

cellSize.height gives the height that the text actually uses when drawn.

user2067021
  • 4,399
  • 37
  • 44
2

I found that NSString#boundingRectWithSize is never exactly correct, the only thing that seems to work well is the one described here: https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/TextLayout/Tasks/StringHeight.html

class func sizeForString(_ text : String, withFont font: NSFont, inWidth maxWidth: CGFloat) -> NSSize {
    let textStorage = NSTextStorage(string: text)
    let textContainer = NSTextContainer(containerSize: NSSize(width: maxWidth, height: CGFloat.greatestFiniteMagnitude))
    let layoutManager = NSLayoutManager()
    layoutManager.addTextContainer(textContainer)
    textStorage.addLayoutManager(layoutManager)
    textStorage.addAttribute(NSFontAttributeName, value: font, range: NSMakeRange(0, textStorage.length))
textContainer.lineFragmentPadding = 0
    layoutManager.glyphRange(for: textContainer)
    return layoutManager.usedRect(for: textContainer).size
 }
valR
  • 829
  • 7
  • 13
1

This is what I ended up going with (at least for the time being) it seems to work fine, however I still feel like there is a better way to accomplish this.

NSString *temp = @"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer vel felis nec massa ultricies blandit non id arcu. Sed enim est. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer vel felis nec massa ultricies blandit non id arcu. Sed enim est.";

myText.stringValue = temp;

NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys:
                            [NSFont systemFontOfSize:12], NSFontAttributeName,
                            [NSParagraphStyle defaultParagraphStyle], NSParagraphStyleAttributeName,
                            nil];

NSSize size = NSMakeSize(window.frame.size.width, MAXFLOAT);


myText.frame = [temp boundingRectWithSize:size options:NSLineBreakByWordWrapping | NSStringDrawingUsesLineFragmentOrigin attributes:attributes];

// Keep current width but add some more on the bottom
NSSize tF = myText.frame.size;
[myText setFrameSize:NSMakeSize(tF.width, tF.height + 35)];

[myText setFrameOrigin:NSMakePoint((NSWidth(window.frame) - NSWidth(myText.frame)) / 2, 
                                   (NSHeight(window.frame) -  NSHeight(myText.frame) - 20))];

[myText setAutoresizingMask:NSViewMinXMargin | NSViewMaxXMargin | NSViewMinYMargin | NSViewMaxYMargin];

Produces this:

enter image description here

Desh__
  • 899
  • 1
  • 10
  • 16