NSString boundingRectWithSize slightly underestimating the correct height - why?

情到浓时终转凉″ 提交于 2019-11-29 03:41:29
r-dent

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 3)

An alternative Swift 3 way to do this would be:

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

See this answer for an Objective-C example.

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.

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:

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
 }
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!