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

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

Related

UILabel and .usesFontLeading

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)
}
}

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)
}
}

get height of dynamic textViews in tableView without using automatic dimensions using swift

I want to calculate the height of my textView's in a tableView dynamically to use in heightForRowAt. I DO NOT want to use automatic dimensions as it often messes up my scrolling in containerViews.
Presently I am instantiating a textView for every cell, adding the text and getting the height using:
var textViewForCellHeight = UITextView()
textViewForCellHeight.font = UIFont.preferredFont(forTextStyle: UIFontTextStyle.body)
textViewForCellHeight.frame = CGRect(x: 0, y: 0, width: self.tableView.frame.size.width - cellHorizontalPadding - tableView.safeAreaInsets.left - tableView.safeAreaInsets.right, height: 0)
textViewForCellHeight.text = myString
textViewForCellHeight.sizeToFit()
return textViewForCellHeight.frame.size.height
Using this in heightForRowAt works fine and gives the correct height for the cell, but it expensive and slows down the tableView considerably. Is there a more efficient way to get the height of the tableView cell's dynamically with a textView?
You can simply pass your string in this function and you will get dynamic height according to your string.
func calculateHeight(inString:String) -> CGFloat
{
let messageString = input.text
let attributes : [NSAttributedStringKey : Any] = [NSAttributedStringKey(rawValue: NSAttributedStringKey.font.rawValue) : UIFont.systemFont(ofSize: 15.0)]
let attributedString : NSAttributedString = NSAttributedString(string: messageString!, attributes: attributes)
let rect : CGRect = attributedString.boundingRect(with: CGSize(width: 222.0, height: CGFloat.greatestFiniteMagnitude), options: .usesLineFragmentOrigin, context: nil)
let requredSize:CGRect = rect
return requiredSize.height
}
Try this code:
func cellHeight(withTxt string: String) -> Float {
let textRect: CGRect = string.boundingRect(with: CGSize(width: self.view.frame.width/* Preferred textView Width */, height: CGFloat(MAXFLOAT)), options: ([.usesLineFragmentOrigin, .usesFontLeading]), attributes: [.font: UIFont(name: "Helvetica Neue", size: 17)!], context: nil)
let requiredSize: CGSize = textRect.size
//finally u return your height
return Float(requiredSize.height)
}

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 : boundingRectWithSize return wrong height for a multiline label

I'm using an extension on the String class to get the height needed to display my string in a multiline label.
extension String {
func heightWithConstrainedWidth(width: CGFloat, font: UIFont) -> CGFloat {
let constraintRect = CGSize(width: width, height: CGFloat.max)
let option = NSStringDrawingOptions.UsesFontLeading.union(.UsesLineFragmentOrigin)
let boundingBox = self.boundingRectWithSize(constraintRect, options: option, attributes: [NSFontAttributeName: font], context: nil)
print("ex width \(boundingBox.width)")
print("ex height \(boundingBox.height)")
return boundingBox.height
}
}
I'm calling it in heightForRowAtIndexPath
let myRandomString = "My baby looks like she's ashamed and wants to apologise for something bad she did."
let labelWidth = UIScreen.mainScreen().bounds.width-16
let labelHeight = myRandomString.heightWithConstrainedWidth(labelWidth,font: UIFont.boldSystemFontOfSize(17))
and I get :
ex width 359.0
ex height 40.57421875
But when I use the view debuging option of xcode I get this :
And this is how my UILabel is setup :
As you can see my width is correct (I'm forcing it) but my height is incorrect and I can't figure out why.
Thanks and regards,
Eric
For multiline only use .UsesLineFragmentOrigin.

Resources