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:
-[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.
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.
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.
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
}
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
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)")