Figure out size of UILabel based on String in Swift
IosIphoneStringSwift3UilabelIos Problem Overview
I am trying to calculate the height of a UILabel based on different String lengths.
func calculateContentHeight() -> CGFloat{
var maxLabelSize: CGSize = CGSizeMake(frame.size.width - 48, CGFloat(9999))
var contentNSString = contentText as NSString
var expectedLabelSize = contentNSString.boundingRectWithSize(maxLabelSize, options: NSStringDrawingOptions.UsesLineFragmentOrigin, attributes: [NSFontAttributeName: UIFont.systemFontOfSize(16.0)], context: nil)
print("\(expectedLabelSize)")
return expectedLabelSize.size.height
}
Above is the current function I use to determine the height but it is not working. I would greatly appreciate any help I can get. I would perfer the answer in Swift and not Objective C.
Ios Solutions
Solution 1 - Ios
Use an extension on String
Swift 3
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 ceil(boundingBox.height)
}
func width(withConstrainedHeight height: CGFloat, font: UIFont) -> CGFloat {
let constraintRect = CGSize(width: .greatestFiniteMagnitude, height: height)
let boundingBox = self.boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, attributes: [NSFontAttributeName: font], context: nil)
return ceil(boundingBox.width)
}
}
and also on NSAttributedString
(which is very useful at times)
extension NSAttributedString {
func height(withConstrainedWidth width: CGFloat) -> CGFloat {
let constraintRect = CGSize(width: width, height: .greatestFiniteMagnitude)
let boundingBox = boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, context: nil)
return ceil(boundingBox.height)
}
func width(withConstrainedHeight height: CGFloat) -> CGFloat {
let constraintRect = CGSize(width: .greatestFiniteMagnitude, height: height)
let boundingBox = boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, context: nil)
return ceil(boundingBox.width)
}
}
Swift 4 & 5
Just change the value for attributes
in the extension String
methods
from
[NSFontAttributeName: font]
to
[.font : font]
Solution 2 - Ios
For multiline text this answer is not working correctly. You can build a different String extension by using UILabel
extension String {
func height(constraintedWidth width: CGFloat, font: UIFont) -> CGFloat {
let label = UILabel(frame: CGRect(x: 0, y: 0, width: width, height: .greatestFiniteMagnitude))
label.numberOfLines = 0
label.text = self
label.font = font
label.sizeToFit()
return label.frame.height
}
}
The UILabel gets a fixed width and the .numberOfLines is set to 0. By adding the text and calling .sizeToFit() it automatically adjusts to the correct height.
Code is written in Swift 3
Solution 3 - Ios
Heres a simple solution thats working for me... similar to some of the others posted, but it doesn't not include the need for calling sizeToFit
Note this is written in Swift 5
let lbl = UILabel()
lbl.numberOfLines = 0
lbl.font = UIFont.systemFont(ofSize: 12) // make sure you set this correctly
lbl.text = "My text that may or may not wrap lines..."
let width = 100.0 // the width of the view you are constraint to, keep in mind any applied margins here
let height = lbl.systemLayoutSizeFitting(CGSize(width: width, height: UIView.layoutFittingCompressedSize.height), withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel).height
This handles line wrapping and such. Not the most elegant code, but it gets the job done.
Solution 4 - Ios
This is my answer in Swift 4.1 and Xcode 9.4.1
//This is your label
let proNameLbl = UILabel(frame: CGRect(x: 0, y: 20, width: 300, height: height))
proNameLbl.text = "This is your text"
proNameLbl.font = UIFont.systemFont(ofSize: 17)
proNameLbl.numberOfLines = 0
proNameLbl.lineBreakMode = .byWordWrapping
infoView.addSubview(proNameLbl)
//Function to calculate height for label based on text
func heightForView(text:String, font:UIFont, width:CGFloat) -> CGFloat {
let label:UILabel = UILabel(frame: CGRect(x: 0, y: 0, width: width, height: CGFloat.greatestFiniteMagnitude))
label.numberOfLines = 0
label.lineBreakMode = NSLineBreakMode.byWordWrapping
label.font = font
label.text = text
label.sizeToFit()
return label.frame.height
}
Now you call this function
//Call this function
let height = heightForView(text: "This is your text", font: UIFont.systemFont(ofSize: 17), width: 300)
print(height)//Output : 41.0
Solution 5 - Ios
extension String{
func widthWithConstrainedHeight(_ height: CGFloat, font: UIFont) -> CGFloat {
let constraintRect = CGSize(width: CGFloat.greatestFiniteMagnitude, height: height)
let boundingBox = self.boundingRect(with: constraintRect, options: NSStringDrawingOptions.usesLineFragmentOrigin, attributes: [NSFontAttributeName: font], context: nil)
return ceil(boundingBox.width)
}
func heightWithConstrainedWidth(_ width: CGFloat, font: UIFont) -> CGFloat? {
let constraintRect = CGSize(width: width, height: CGFloat.greatestFiniteMagnitude)
let boundingBox = self.boundingRect(with: constraintRect, options: NSStringDrawingOptions.usesLineFragmentOrigin, attributes: [NSFontAttributeName: font], context: nil)
return ceil(boundingBox.height)
}
}
Solution 6 - Ios
Swift 5:
If you have UILabel and someway boundingRect isn't working for you (I faced this problem. It always returned 1 line height.) there is an extension to easily calculate label size.
extension UILabel {
func getSize(constrainedWidth: CGFloat) -> CGSize {
return systemLayoutSizeFitting(CGSize(width: constrainedWidth, height: UIView.layoutFittingCompressedSize.height), withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel)
}
}
You can use it like this:
let label = UILabel()
label.text = "My text\nIs\nAwesome"
let labelSize = label.getSize(constrainedWidth:200.0)
Works for me
Solution 7 - Ios
I found that the accepted answer worked for a fixed width, but not a fixed height. For a fixed height, it would just increase the width to fit everything on one line, unless there was a line break in the text.
The width function calls the height function multiple times, but it is a quick calculation and I didn't notice performance issues using the function in the rows of a UITable.
extension String {
public 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)
}
public func width(withConstrainedHeight height: CGFloat, font: UIFont, minimumTextWrapWidth:CGFloat) -> CGFloat {
var textWidth:CGFloat = minimumTextWrapWidth
let incrementWidth:CGFloat = minimumTextWrapWidth * 0.1
var textHeight:CGFloat = self.height(withConstrainedWidth: textWidth, font: font)
//Increase width by 10% of minimumTextWrapWidth until minimum width found that makes the text fit within the specified height
while textHeight > height {
textWidth += incrementWidth
textHeight = self.height(withConstrainedWidth: textWidth, font: font)
}
return ceil(textWidth)
}
}
Solution 8 - Ios
Check label text height and it is working on it
let labelTextSize = ((labelDescription.text)! as NSString).boundingRect(
with: CGSize(width: labelDescription.frame.width, height: .greatestFiniteMagnitude),
options: .usesLineFragmentOrigin,
attributes: [.font: labelDescription.font],
context: nil).size
if labelTextSize.height > labelDescription.bounds.height {
viewMoreOrLess.hide(byHeight: false)
viewLess.hide(byHeight: false)
}
else {
viewMoreOrLess.hide(byHeight: true)
viewLess.hide(byHeight: true)
}
Solution 9 - Ios
This solution will help to calculate the height and width at runtime.
let messageText = "Your Text String"
let size = CGSize.init(width: 250, height: 1000)
let options = NSStringDrawingOptions.usesFontLeading.union(.usesLineFragmentOrigin)
let estimateFrame = NSString(string: messageText).boundingRect(with: size, options: options, attributes: [NSAttributedString.Key.font: UIFont(name: "HelveticaNeue", size: 17)!], context: nil)
Here you can calculate the estimated height that your string would take and pass it to the UILabel frame.
estimateFrame.Width
estimateFrame.Height
Solution 10 - Ios
In Swift 5:
label.textRect(forBounds: label.bounds, limitedToNumberOfLines: 1)
btw, the value of limitedToNumberOfLines
depends on your label's text lines you want.
Solution 11 - Ios
I was not able to get @KaanDedeoglu's solution to work in Swift 5 for multiline labels and text views - for whatever reason - so I just ended up writing a 'by hand' solution keeping the same function signatures as seen in @KaanDedeoglu's answer for those who are interested. Works like a charm for the uses in my program.
Width
extension String {
func width(withConstrainedHeight height: CGFloat, font: UIFont) -> CGFloat {
var wordBoxes = [CGSize]()
var calculatedHeight = CGFloat.zero
var calculatedWidth = CGFloat.zero
for word in self.wordsWithWordSeparators() {
let box = word.boundingRect(with: CGSize.zero, attributes: [.font: font], context: nil)
let boxSize = CGSize(width: box.width, height: box.height)
wordBoxes.append(boxSize)
calculatedHeight += boxSize.height
calculatedWidth = calculatedWidth < boxSize.width ? boxSize.width : calculatedWidth
}
while calculatedHeight > height && wordBoxes.count > 1 {
var bestLineToRelocate = wordBoxes.count - 1
for i in 1..<wordBoxes.count {
let bestPotentialWidth = wordBoxes[bestLineToRelocate - 1].width + wordBoxes[bestLineToRelocate].width
let thisPotentialWidth = wordBoxes[i - 1].width + wordBoxes[i].width
if bestPotentialWidth > thisPotentialWidth {
bestLineToRelocate = i
}
}
calculatedHeight -= wordBoxes[bestLineToRelocate].height
wordBoxes[bestLineToRelocate - 1].width += wordBoxes[bestLineToRelocate].width
wordBoxes.remove(at: bestLineToRelocate)
calculatedWidth = max(wordBoxes[bestLineToRelocate - 1].width, calculatedWidth)
}
return ceil(calculatedWidth)
}
}
Height
extension String {
func height(withConstrainedWidth width: CGFloat, font: UIFont) -> CGFloat {
var wordBoxes = [CGSize]()
var calculatedHeight = CGFloat.zero
var currentLine = 0
for word in self.wordsWithWordSeparators() {
let box = word.boundingRect(with: CGSize.zero, attributes: [.font: font], context: nil)
let boxSize = CGSize(width: box.width, height: box.height)
if wordBoxes.isEmpty == true {
wordBoxes.append(boxSize)
}
else if wordBoxes[currentLine].width + boxSize.width > width {
wordBoxes.append(boxSize)
currentLine += 1
}
else {
wordBoxes[currentLine].width += boxSize.width
wordBoxes[currentLine].height = max(wordBoxes[currentLine].height, boxSize.height)
}
}
for wordBox in wordBoxes {
calculatedHeight += wordBox.height
}
return calculatedHeight
}
}
Helper Methods Used
extension String {
// Should work with any language supported by Apple
func wordsWithWordSeparators () -> [String] {
let range = self.startIndex..<self.endIndex
var words = [String]()
self.enumerateSubstrings(in: range, options: .byWords) { (substr, substrRange, enclosingRange, stop) in
let wordWithWordSeparators = String(self[enclosingRange])
words.append(wordWithWordSeparators)
}
return words
}
}
Note: these height and width calculations assume the given label or text view will not split or hyphenate words when performing line breaks. If this is not the case for you, you should only have to substitute words for characters. Also, if you're in a runtime sensitive environment, may want to consider throttling these function calls or caching results since they could be a bit expensive depending on how many words the string contains.
Solution 12 - Ios
@IBOutlet weak var constraintTxtV: NSLayoutConstraint!
func TextViewDynamicallyIncreaseSize() {
let contentSize = self.txtVDetails.sizeThatFits(self.txtVDetails.bounds.size)
let higntcons = contentSize.height
constraintTxtV.constant = higntcons
}