Reputation:
I calculate the height of an NSAttributedString
like this.
let maxSize = NSSize(width: w, height: CGFloat.greatestFiniteMagnitude)
let rect = boundingRect(with: maxSize, options: [.usesFontLeading, .usesLineFragmentOrigin])
let height = rect.integral.size.height
I tried about every "hack" that was mentioned on SO, yet the string height gets more inaccurate the smaller the width gets (the calculated height is larger than the actual height).
According to other posts the following things cause problems with size calculation:
I have found that none of these suggestions make any difference. The attributed string is a concatenation of lines, each having a foregroundColor
and backgroundColor
. The string also has the following paragraph style.
let pstyle = NSMutableParagraphStyle()
pstyle.lineSpacing = 6
pstyle.lineBreakMode = .byWordWrapping
and a userFixedPitchFont
of size 11.
Why does the height error get larger the smaller the width is?
PS: I imagine it has something to do with lineBreak
since the error gets larger if more lines are word wrapped.
Upvotes: 2
Views: 3419
Reputation:
I found that this solution works. Note that if you don't set the lineFragmentPadding
to 0
, it produces the same (wrong) results as boundingRect
.
extension NSAttributedString {
func sizeFittingWidth(_ w: CGFloat) -> CGSize {
let textStorage = NSTextStorage(attributedString: self)
let size = CGSize(width: w, height: CGFloat.greatestFiniteMagnitude)
let boundingRect = CGRect(origin: .zero, size: size)
let textContainer = NSTextContainer(size: size)
textContainer.lineFragmentPadding = 0
let layoutManager = NSLayoutManager()
layoutManager.addTextContainer(textContainer)
textStorage.addLayoutManager(layoutManager)
layoutManager.glyphRange(forBoundingRect: boundingRect, in: textContainer)
let rect = layoutManager.usedRect(for: textContainer)
return rect.integral.size
}
}
Upvotes: 11