I have a UITextView containing an NSAttributedString. I want to size the text view so that, given a fixed width, it shows the entire string without scrolling.
NSAttributedString has a method which allows to compute its bounding rect for a given size
let computedSize = attributedString.boundingRect(with: CGSize(width: 200, height: CGFloat.greatestFiniteMagnitude),
options: .usesLineFragmentOrigin,
context: nil)
But unfortunately it seems not working, since it always returns the height of a single line.
After several attempts, I figured out that the
NSAttributedStringI was setting hadbyTruncatingTailaslineBreakModevalue forNSParagraphStyle(which is the default value we use in our application).To achieve the desired behaviour I have to change it to
byWordWrappingorbyCharWrapping.Note that when setting the attributed string with
byTruncatingTailvalue on aUILabel(wherenumberOfLinesvalue is 0), the string is "automatically" sized to be multiline, which doesn't happen when computing theboundingRect.There are other factors to keep in mind when computing
NSAttributedStringheight for use inside aUITextView(each one of these can cause the string not to be entirely contained in the text view):1. Recompute height when bounds change
Since height is based on bounds, it should be recomputed when bounds change. This can be achieved using KVO on
boundskeypath, invalidating the layout when this change.In my case I'm invalidating
intrinsicContentSizeof my customUITextViewsince is the way I size it based on the computed string height.2. Use
NSTextContainerwidthUse
textContainer.width(instead ofbounds.width) as the fixed width to use forboundingRectmethod call, since it keeps anytextContainerInsetvalue into account (althoughleftandrightdefault values are 0)3. Add vertical
textContainerInsetsvalues to string heightAfter computing
NSAttributedStringheight we should addtextContainerInsets.topandtextContainerInsets.bottomto compute the correctUITextFieldheight (their default values is 8.0...)4. Remove lineFragmentPadding
Set 0 as value of
lineFragmentPaddingor, if you want to have it, remember to remove its value from the "fixed width" before computingNSAttributedStringheight5. Apply
ceilto computed heightThe height value returned by
boundingRectcan be fractional, if we use as it is it can potentially cause the last line not to be shown. Pass it to theceilfunction to obtain the upper integer value, to avoid down rounding.