UILabel and .usesFontLeading - ios

I researched a bit and it looks like the default value for NSLayoutManager's .usesFontLeading value is true.
My problem:
I'm using boundingRect to get the size of the text in UILabel with a custom font, the height is correct if I remove .usesFontLeading but incorrect if I add that option.
My question:
In UILabel, does the font of my label automatically use my font's leading value? and secondly, am I required to set .usesFontLeading as an option in my boundingRect.

Try this:
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, .usesFontLeading],
attributes: [.font: font],
context: nil)
return ceil(boundingBox.height)
}
}

Related

Uilabel height using boundigRect

Hi everyone I have created a view that shows an error message. Within this view I have inserted a UILabel that shows the message. So far so good, the height of the UILabel changes based on the length of the text using text.boundingRect
My problem is that the text is shown correctly only if it does not exceed a certain number of lines, in case the text is too long it is cut and I don't understand why
In short, if the text is not very long I have no display problems otherwise if the text is very long it is cut
this is what i am using to get the height of the text.
Where am I wrong?
private func estimateTextHeight()-> CGFloat {
let text = (toastView.textLbl.text ?? "") as NSString
let attribute: [NSAttributedString.Key: Any] = [.font: toastView.textLbl.font!]
return text.boundingRect(with: .init(width: 300, height: 2000), options: .usesLineFragmentOrigin, attributes: attribute, context: nil).height
}
private func updateToast(icon: UIImage?) -> Void {
let height = estimateTextHeight()
toastView.heightAnchor.constraint(equalToConstant: height).isActive = true
}
To calculate label height try this:
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: [.font: font],
context: nil)
return ceil(boundingBox.height)
}
}

Swift 4 in Xcode 9 - How to use boundingrect in NSAttributedstring?

I want to get the length of some texts in UILabel, and change the width property of UILabel dynamically.
But I have no idea about how to set the parameters in the function boundingrect.
This is the documentation of Apple Developer.
func boundingRect(with size: CGSize,
options: NSStringDrawingOptions = [],
context: NSStringDrawingContext?) -> CGRect
,and I tried like this
let attr = NSMutableAttributedString(string: "test test test test , this is test text. test test test test , this is test text. test test test test , this is test text. ")
//attr.addattributes
print(attr.boundingrect(with: CGSize(width: CGFloat.greatestFiniteMagnitude, height: 0), option: .truncatesLastVisibleLine
,context: nil)!)
But finally I got false width in print,so why and how to get the correct one.
Use
public extension NSAttributedString {
public func width(height: CGFloat) -> CGFloat {
let constraintRect = CGSize(width: .greatestFiniteMagnitude, height: height)
let boundingBox = self.boundingRect(with: constraintRect,
options: [.usesLineFragmentOrigin, .usesFontLeading],
context: nil)
return ceil(boundingBox.height)
}
}
and then get let width = attr.width(height: height)
where height > 0

Calculating attributed string height

I'm trying to set a tableViewCell height equal to the height of the attributedString inside the cell. However whatever I do it does not seem to have the correctSize. this is what I've tried so far
cellHeight
//Convert description to NSAttributedString and get height
//width is equal to screen width - right and left offset
//at the end add the bottom and height offset to the cell height
return detailPetViewModel!.description
.lineSpacing(spacing: 4)
.heightWithConstrainedWidth(width: Sizes.screenWidth - 40) + 10
Customize descLabel in cell subclass
//Customize descLabel
descLabel.font = FontFamily.Avenir.Regular.font(size: 16)
descLabel.textColor = UIColor(named: .SecondaryTextColor)
//Multiple lines
descLabel.numberOfLines = 0
descLabel.lineBreakMode = .byWordWrapping
descLabel.sizeToFit()
linespacing extension
extension String {
func lineSpacing(spacing: CGFloat) -> NSAttributedString {
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineSpacing = spacing
let attributedString = NSMutableAttributedString(string: self)
attributedString.addAttribute(NSParagraphStyleAttributeName, value:paragraphStyle, range:NSMakeRange(0, attributedString.length))
return attributedString
}
}
height Extension
extension NSAttributedString {
func heightWithConstrainedWidth(width: CGFloat) -> CGFloat {
let constraintRect = CGSize(width: width, height: .greatestFiniteMagnitude)
let boundingBox = self.boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, context: nil)
return boundingBox.height
}
}
This is the extension I use. You shouldn't have to pass in any information about the font or line-height as the attributed string already knowns its values.
extension NSAttributedString {
func height(containerWidth: CGFloat) -> CGFloat {
let rect = self.boundingRect(with: CGSize.init(width: containerWidth, height: CGFloat.greatestFiniteMagnitude),
options: [.usesLineFragmentOrigin, .usesFontLeading],
context: nil)
return ceil(rect.size.height)
}
func width(containerHeight: CGFloat) -> CGFloat {
let rect = self.boundingRect(with: CGSize.init(width: CGFloat.greatestFiniteMagnitude, height: containerHeight),
options: [.usesLineFragmentOrigin, .usesFontLeading],
context: nil)
return ceil(rect.size.width)
}
}
Used like this:
let height = detailPetViewModel!.description.height(containerWidth: Sizes.screenWidth - 40 + 10)
You can simply use (the instance of NSAttributedString).size.height
e.g.
let a = NSAttributedString(string: "A string", attributes: [NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 14)])
let height = a.size().height

Swift How to calculate one line text height from its font

I ran into an issue where I needed to animate translating a label vertically the same distance of a textField's text height. In most cases just textField.bounds.heigt but if the textField's height is bigger than the text height it will not be any good for me. So I need to know:
How to calculate the line height of the string text from its UIFont?
Regarding the duplicate:
There's a little bit different of what I need. that answer(which I've referenced in my answer) get the total height depending on 1) the string 2) the width 3) the font. What I needed is one line height dpending only on the font.
UIFont has a property lineHeight:
if let font = _textView.font {
let height = font.lineHeight
}
where font is your font
I have been searching for a way to do that and find this answer where it has a String extension to calculate the size for the string and a given font. I have modified it to do what I want (get the line height of text written using a font.):
extension UIFont {
func calculateHeight(text: String, width: CGFloat) -> CGFloat {
let constraintRect = CGSize(width: width, height: CGFloat.greatestFiniteMagnitude)
let boundingBox = text.boundingRect(with: constraintRect,
options: NSStringDrawingOptions.usesLineFragmentOrigin,
attributes: [NSAttributedStringKey.font: self],
context: nil)
return boundingBox.height
}
}
I hope this helps someone looking for it. (may be myself in the future).
I've used this String extension in the past to draw some text as opposed to creating a UILabel somewhere. I don't like the fact that I can't seem to get the real height of the specific text I want to draw (not every string contains capital letters or characters with descenders, etc.) I've used a couple of enums for horizontal and vertical alignment around the given point. Open to ideas on the vertical height.
public func draw(at pt: CGPoint,
font: UIFont? = UIFont.systemFont(ofSize: 12),
color: UIColor? = .black,
align: HorizontalAlignment? = .Center,
vAlign: VerticalAlignment? = .Middle)
{
let attributes: [NSAttributedString.Key : Any] = [.font: font!,
.foregroundColor: color!]
let size = self.boundingRect(with: CGSize(width: 0, height: 0),
options: [ .usesFontLeading ],
attributes: [ .font: font! ],
context: nil).size
var x = pt.x
var y = pt.y
if align == .Center {
x -= (size.width / 2)
} else if align == .Right {
x -= size.width
}
if vAlign == .Middle {
y -= (size.height / 2)
} else if vAlign == .Bottom {
y -= size.height
}
let rect = CGRect(x: x, y: y, width: size.width, height: size.height)
draw(in: rect, withAttributes: attributes)
}

Wrong text height when text contains emoji

Following the official docs, I created this function to calculate text height.
func calculateTextHeight(myString: String, myWidth: CGFloat, myFont: UIFont) -> CGFloat {
let textStorage = NSTextStorage(string: myString)
let textContainer = NSTextContainer(size: CGSize(width: myWidth, height: CGFloat.max))
let layoutManager = NSLayoutManager()
layoutManager.addTextContainer(textContainer)
textStorage.addLayoutManager(layoutManager)
textStorage.addAttribute(NSFontAttributeName, value: myFont, range: NSMakeRange(0, textStorage.length))
textContainer.lineFragmentPadding = 0
textContainer.lineBreakMode = .ByWordWrapping
layoutManager.glyphRangeForTextContainer(textContainer)
return layoutManager.usedRectForTextContainer(textContainer).size.height
}
But the calculated height is wrong when the text contains an emoji.
var s = "ABCDE 12345"
print(calculateTextHeight(s, myWidth: 500, myFont: UIFont.systemFontOfSize(14)))
// prints 16.7 (correct)
s = "ABCDE 12345 💩"
print(calculateTextHeight(s, myWidth: 500, myFont: UIFont.systemFontOfSize(14)))
// prints 22.9 (should be 16.7)
Is this a bug? How can I fix this?
This worked for me when text contains emojis. For NSAttributedString strings:
extension NSAttributedString {
func sizeOfString(constrainedToWidth width: Double) -> CGSize {
let framesetter = CTFramesetterCreateWithAttributedString(self)
return CTFramesetterSuggestFrameSizeWithConstraints(framesetter, CFRange(location: 0, length: 0), nil, CGSize(width: width, height: .greatestFiniteMagnitude), nil)
}
}
For String:
extension String {
func sizeOfString(constrainedToWidth width: Double, font: UIFont) -> CGSize {
let attributes = [NSAttributedString.Key.font : font]
let attString = NSAttributedString(string: self, attributes: attributes)
let framesetter = CTFramesetterCreateWithAttributedString(attString)
return CTFramesetterSuggestFrameSizeWithConstraints(framesetter, CFRange(location: 0, length: 0), nil, CGSize(width: width, height: .greatestFiniteMagnitude), nil)
}
}
I used an alternate method to calculate text height. This works with emojis.
static func calculateStringHeight(str: String, maxWidth: CGFloat, font: UIFont) -> CGFloat {
return str.boundingRectWithSize(CGSizeMake(maxWidth, CGFloat.max), options: NSStringDrawingOptions.UsesLineFragmentOrigin, attributes: [NSFontAttributeName: font], context: nil).height
}
I do not think it is a bug. Emoji take up more space to be displayed.
I believe this will make a difference only if the number of emoji in your text is too large.
If you try the code below, i think that the result will be the same.
s = "ABCDE 12345 💩💩💩💩💩💩💩"
print(calculateTextHeight(s, myWidth: 500, myFont: UIFont.systemFontOfSize(14)))
// prints 22.9
If you want to eliminate the emoji, you can remove them from the original text before doing the height calculation.
In this case, you need to scan the original text by replacing all emoji by other character, and then call the height calculation.

Resources