Reputation: 21880
I have a UITableView with variably sized cells. The height is based on a block of text that I need to display so I need to measure the text.
I'm just using some Lorem Ipsum text:
"Nullam est elit, rutrum volutpat scelerisque eu, tincidunt ut magna. Quisque sit amet odio quis eros lobortis cursus. Phasellus mauris augue, mattis a posuere sit amet, dictum sit amet ipsum. Praesent velit sapien, tristique ut pellentesque eget, venenatis in est. Curabitur accumsan lacus nec ultricies finibus. In commodo, turpis at auctor lobortis, augue ipsum tincidunt lorem, vitae fringilla libero magna eget tortor. Sed et ultrices odio, et viverra diam. Mauris semper non lorem varius sollicitudin. Maecenas nec dui lectus. In hac habitasse platea dictumst. Quisque sapien ex, pretium id vehicula eget, tempus at nisl. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Aliquam vel sollicitudin velit, efficitur accumsan sem. Quisque id urna blandit, cursus justo sed, aliquet velit. Nullam id ipsum pellentesque, gravida nulla ut, tempor libero."
When I call ceil(text.height())
it returns 386.0
tall, but when I put that text into the UITextView and print out the content size of the UITextView it says the content is 422.0
tall. (i.e. my text is getting cut off because the UITextView isn't tall enough to display everything.)
Why is there a difference?
I'm using the following String extension to calculate the height of the text:
extension String {
func height(withConstrainedWidth width: CGFloat, font: UIFont) -> CGFloat {
let constraintRect = CGSize(width: width, height: .greatestFiniteMagnitude)
let boundingBox = self.boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, attributes: [NSFontAttributeName: font], context: nil)
return boundingBox.height
}
}
Then in tableView(heightForRowAt:)
I calculate the height of the text and add it to the space above and below the textview to get the full cell height (this was more elegant before I started debugging this):
let textviewWidth = self.view.frame.size.width-CGFloat(32)
let spaceAboveTextView = CGFloat(45)
let spaceBelowTextView = CGFloat(16)
let textHeight = ceil(text.height(withConstrainedWidth: textviewWidth, font: UIFont.systemFont(ofSize: 17.0)))
let height = spaceAboveTextView + textHeight + spaceBelowTextView
I've verified that the UITextView is using the System Font of size 17, which matches with my code above.
I've verified the frame of the UITextView (after being displayed) is (16.0, 45.0, 382.0, 385.6667)
so the spaceAboveTextView=45
is accurate and the width=382
is accurate. I've also checked the constraints on the UITextView and it has a bottom constraint of 8pt to the margin and the margin is 8pt for a total of 16pt in the spaceBelowTextView
as well.
So, what am I doing wrong?
Upvotes: 3
Views: 1501
Reputation: 6272
Most likely you have a problem because you forgot to factor in the textContainerInset
(maybe also other insets of your text view).
By default, they are 8 from top, left and right.
Try to change your code to this:
let textviewWidth = self.view.frame.size.width-CGFloat(32)-textView.textContainerInset.left-textView.textContainerInset.right
let spaceAboveTextView = CGFloat(45)+textView.textContainerInset.top
// bottom space is 0 by default. But lets add it just in case ;)
let spaceBelowTextView = CGFloat(16)+textView.textContainerInset.bottom
let textHeight = ceil(text.height(withConstrainedWidth: textviewWidth, font: UIFont.systemFont(ofSize: 17.0)))
let height = spaceAboveTextView + textHeight + spaceBelowTextView
I just tested this solution and it works for me with system font of size 17. Please let me know if this doesn't work!
Upvotes: 4
Reputation: 23701
Measuring text for display on the screen is difficult because the system changes fonts and spacing and such to make the text more legible on the CRT (for example it can substitute screen fonts for vector fonts). Even then it's possible to have some overlaps when using characters with high ascent or descent like Å.
NSString and NSAttributedStrings have measurement routines that calculate the bounding size of text in the current graphics context. See boundingRectWithSize:options:attributes:context:. In the past what I've done to force raster metrics when I didn't have a convenient CGContext was create a small offscreen bitmap context (say 1 pixel in size) and set that to the current context while measuring the text.
Another thing you want to look for is the fact that UITextView draws into it's textContainer
and not into it's frame. IIRC the default container is the frame inset by 4 or 5 pixels.
Upvotes: 1