How to get height for NSAttributedString at a fixed width

无人久伴 提交于 2019-11-27 09:50:26

问题


I want to do some drawing of NSAttributedStrings in fixed-width boxes, but am having trouble calculating the right height they'll take up when drawn. So far, I've tried:

  1. Calling - (NSSize) size, but the results are useless (for this purpose), as they'll give whatever width the string desires.

  2. Calling - (void)drawWithRect:(NSRect)rect options:(NSStringDrawingOptions)options with a rect shaped to the width I want and NSStringDrawingUsesLineFragmentOrigin in the options, exactly as I'm using in my drawing. The results are ... difficult to understand; certainly not what I'm looking for. (As is pointed out in a number of places, including this Cocoa-Dev thread).

  3. Creating a temporary NSTextView and doing:
    [[tmpView textStorage] setAttributedString:aString];
    [tmpView setHorizontallyResizable:NO];
    [tmpView sizeToFit];

    When I query the frame of tmpView, the width is still as desired, and the height is often correct ... until I get to longer strings, when it's often half the size that's required. (There doesn't seem to be a max size being hit: one frame will be 273.0 high (about 300 too short), the other will be 478.0 (only 60-ish too short)).

I'd appreciate any pointers, if anyone else has managed this.


回答1:


-[NSAttributedString boundingRectWithSize:options:]

You can specify NSStringDrawingUsesDeviceMetrics to get union of all glyph bounds.

Unlike -[NSAttributedString size], the returned NSRect represents the dimensions of the area that would change if the string is drawn.

As @Bryan comments, boundingRectWithSize:options: is deprecated (not recommended) in OS X 10.11 and later. This is because string styling is now dynamic depending on the context.

For OS X 10.11 and later, see Apple's Calculating Text Height developer documentation.




回答2:


The answer is to use
- (void)drawWithRect:(NSRect)rect options:(NSStringDrawingOptions)options
but the rect you pass in should have 0.0 in the dimension you want to be unlimited (which, er, makes perfect sense). Example here.




回答3:


You might be interested in Jerry Krinock's great (OS X only) NS(Attributed)String+Geometrics category, which is designed to do all sorts of string measurement, including what you're looking for.




回答4:


I have a complex attributed string with multiple fonts and got incorrect results with a few of the above answers that I tried first. Using a UITextView gave me the correct height, but was too slow for my use case (sizing collection cells). I wrote swift code using the same general approach described in the Apple doc referenced previously and described by Erik. This gave me correct results with must faster execution than having a UITextView do the calculation.

private func heightForString(_ str : NSAttributedString, width : CGFloat) -> CGFloat {
    let ts = NSTextStorage(attributedString: str)

    let size = CGSize(width:width, height:CGFloat.greatestFiniteMagnitude)

    let tc = NSTextContainer(size: size)
    tc.lineFragmentPadding = 0.0

    let lm = NSLayoutManager()
    lm.addTextContainer(tc)

    ts.addLayoutManager(lm)
    lm.glyphRange(forBoundingRect: CGRect(origin: .zero, size: size), in: tc)

    let rect = lm.usedRect(for: tc)

    return rect.integral.size.height
}



回答5:


Use NSAttributedString method

- (CGRect)boundingRectWithSize:(CGSize)size options:(NSStringDrawingOptions)options context:(NSStringDrawingContext *)context

The size is the constraint on the area, the calculated area width is restricted to the specified width whereas the height is flexible based on this width. One can specify nil for context if that's not available. To get multi-line text size, use NSStringDrawingUsesLineFragmentOrigin for options.




回答6:


On OS X 10.11+, the following method works for me (from Apple's Calculating Text Height document)

- (CGFloat)heightForString:(NSAttributedString *)myString atWidth:(float)myWidth
{
    NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:myString];
    NSTextContainer *textContainer = [[NSTextContainer alloc] initWithContainerSize:
        NSMakeSize(myWidth, FLT_MAX)];
    NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init];
    [layoutManager addTextContainer:textContainer];
    [textStorage addLayoutManager:layoutManager];
    [layoutManager glyphRangeForTextContainer:textContainer];
    return [layoutManager
        usedRectForTextContainer:textContainer].size.height;
}



回答7:


Swift 3:

let attributedStringToMeasure = NSAttributedString(string: textView.text, attributes: [
        NSFontAttributeName: UIFont(name: "GothamPro-Light", size: 15)!,
        NSForegroundColorAttributeName: ClickUpConstants.defaultBlackColor
])

let placeholderTextView = UITextView(frame: CGRect(x: 0, y: 0, width: widthOfActualTextView, height: 10))
placeholderTextView.attributedText = attributedStringToMeasure
let size: CGSize = placeholderTextView.sizeThatFits(CGSize(width: widthOfActualTextView, height: CGFloat.greatestFiniteMagnitude))

height = size.height

This answer works great for me, unlike the other ones which were giving me incorrect heights for larger strings.

If you want to do this with regular text instead of attributed text, do the following:

let placeholderTextView = UITextView(frame: CGRect(x: 0, y: 0, width: ClickUpConstants.screenWidth - 30.0, height: 10))
placeholderTextView.text = "Some text"
let size: CGSize = placeholderTextView.sizeThatFits(CGSize(width: widthOfActualTextView, height: CGFloat.greatestFiniteMagnitude))

height = size.height



回答8:


I just wasted a bunch of time on this, so I'm providing an additional answer to save others in the future. Graham's answer is 90% correct, but it's missing one key piece:

To obtain accurate results with -boundingRectWithSize:options: you MUST pass the following options:

NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesDeviceMetrics|NSStringDrawingUsesFontLeading

If you omit the lineFragmentOrigin one, you'll get nonsense back; the returned rect will be a single line high and won't at all respect the size you pass into the method.

Why this is so complicated and so poorly documented is beyond me. But there you have it. Pass those options and it'll work perfectly (on OS X at least).




回答9:


Not a single answer on this page worked for me, nor did that ancient old Objective-C code from Apple's documentation. What I finally did get to work for a UITextView is first setting its text or attributedText property on it and then calculating the size needed like this:

let size = textView.sizeThatFits(CGSize(width: maxWidth, height: CGFloat.max))

Works perfectly. Booyah!




回答10:


As lots of guys mentioned above, and base on my test.

I use open func boundingRect(with size: CGSize, options: NSStringDrawingOptions = [], context: NSStringDrawingContext?) -> CGRect on iOS like this bellow:

let rect = attributedTitle.boundingRect(with: CGSize(width:200, height:0), options: NSStringDrawingOptions.usesLineFragmentOrigin, context: nil)

Here the 200 is the fixed width as your expected, height I give it 0 since I think it's better to kind tell API height is unlimited.

Option is not so important here,I have try .usesLineFragmentOrigin or .usesLineFragmentOrigin.union(.usesFontLeading) or .usesLineFragmentOrigin.union(.usesFontLeading).union(.usesDeviceMetrics), it give same result.

And the result is expected as my though.

Thanks.




回答11:


I found helper class to find height and width of attributedText (Tested code)

https://gist.github.com/azimin/aa1a79aefa1cec031152fa63401d2292

Add above file in your project

How to use

let attribString = AZTextFrameAttributes(attributedString: lbl.attributedText!)
let width : CGFloat = attribString.calculatedTextWidth()
print("width is :: >> \(width)")



回答12:


Swift 4.2

let attributedString = self.textView.attributedText
let rect = attributedString?.boundingRect(with: CGSize(width: self.textView.frame.width, height: CGFloat.greatestFiniteMagnitude), options: [.usesLineFragmentOrigin, .usesFontLeading], context: nil)
print("attributedString Height = ",rect?.height)


来源:https://stackoverflow.com/questions/2282629/how-to-get-height-for-nsattributedstring-at-a-fixed-width

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