Calculate actual text frame of UILabel - ios

i have an extension to UILabel that suppose to calculate it's frame.
func getFrameWithAlreadySetTextAndFont() -> CGRect {
let label = UILabel()
label.text = self.text
label.font = self.font
label.numberOfLines = 0
label.lineBreakMode = NSLineBreakMode.byWordWrapping
label.frame.size.width = CGFloat(MAXFLOAT)
label.frame.size.height = CGFloat(MAXFLOAT)
label.sizeToFit()
return label.frame
}
Now, i want to calculate expected text frame for my label.
let lbl = LabelSL.extraLarge()
lbl.text = "357"
let expectedLabelFrame = lbl.getFrameWithAlreadySetTextAndFont()
Ok, i got:
expected frame (0.0, 0.0, 42.5, 29.0)
That is frame of label i can see in debugger.
But what i actually want is text frame:
The same as previous, but without extra spaces. How can i get it?

Related

Not getting correct height of UILabel in Objective-c

I have string with multiple new line characters like below.
"sdfsdghhfdgdfgfdgdfghjgf
sdfsdfsdfsdfdsfdsfdsgdfgfdhghgfhfgjhf
sdfdsfdsfdsfdsfdsfdsfhhg
sdfsdfdfsd
sdfdsfdsfdsfdsfdfdsfsdf"
Now I am calculating height of label to accommodate above string. Below is my code for it.
label.numberOfLines = 0;
label.lineBreakMode = NSLineBreakByWordWrapping;
label.font = font;
label.text = labelText;
float width = label.frame.size.width;
CGSize jobDescHeightSize = [label sizeThatFits:CGSizeMake(width, CGFLOAT_MAX)];
It gives me correct height in larger device i.e. iPhone XR, iPhone 11 but it doesn't give me correct size of UILabel in iPhone 6s, iPhone 8.
The extension which solved my issue once
extension UILabel{
public var requiredHeight: CGFloat {
let label = UILabel(frame: CGRect(x: 0, y: 0, width: frame.width, height: CGFloat.greatestFiniteMagnitude))
label.numberOfLines = 0
label.lineBreakMode = NSLineBreakMode.byWordWrapping
label.font = font
label.text = text
label.attributedText = attributedText
label.sizeToFit()
return label.frame.height
}
}
Original Answer

Swift/Cocoa Touch: How do I centre dynamically resized text in a UILabel?

I have a UILabel that takes the size of its parent view.
I want the label text to resize to fit within the containing view.
override func draw(_ rect: CGRect) {
let fontSize = self.frame.size.height * 0.9
var font = UIFont.preferredFont(forTextStyle: .body).withSize(fontSize)
font = UIFontMetrics(forTextStyle: .body).scaledFont(for: font)
let label = UILabel()
label.textAlignment = .center
label.font = font
label.frame.size = self.frame.size
label.minimumScaleFactor = 0.1
label.numberOfLines = 1
label.adjustsFontSizeToFitWidth = true
label.text = "5"
addSubview(label)
}
The above works just how I want. The number 5 is displayed in the centre of the containing view. However if I set the text to something longer (for example: label.text = "5"), its vertical position changes.
How do I ensure that, no matter the length of the string, the text will be centred vertically as well as horizontally.
Add this line of code in your UILabel set up. Should work as expected. This will make the label to center vertically.
label.baselineAdjustment = .alignCenters

Resize UITableViewCell Height based on UILabel Content

I've been looking into this for over a day now, and I can honestly say I am completely stumped by why this is not working as I would expect it to.
I'm trying to have a UITableViewCell expand when selected to the correct size based on the UILabel within it. I have used the following code to determine the required size for the UILabel:
extension UILabel {
func requiredHeight() -> CGFloat{
let label:UILabel = UILabel(frame: CGRectMake(0, 0, self.frame.width, CGFloat.max))
label.text = self.text
label.numberOfLines = 0
label.font = self.font
label.sizeToFit()
print("Final Size - \(label.frame.height)")
return label.frame.height + 10
}
}
My issue is, despite this size - when the Cell is resized within the 'heightForRowAtIndexPath' method - it is still not the correct size and the string is being truncated, this can be seen in the below image.
To note - I gather the required size of the cell as soon as the view has loaded and text has been populated into the UILabel.
requiredHeight = overviewLabel.requiredHeight()
if requiredHeight > overviewCell.frame.height {
expander.hidden = false
} else {
expander.hidden = true
}
Any advice on how this could be fixed will be greatly appreciated.
Try this snippet. Just provide exact name of the font and size
Swift 3.x
func requiredHeight() -> CGFloat{
let font = UIFont(name: "Helvetica", size: 16.0)
let label:UILabel = UILabel(frame: CGRect(x:0, y:0, width:200, height:CGFloat.greatestFiniteMagnitude))
label.numberOfLines = 0
label.lineBreakMode = NSLineBreakMode.byWordWrapping
label.font = font
label.text = self.text
label.sizeToFit()
return label.frame.height + //Add some space as a part of your bottom and top constraint
}
Swift 2.2
func requiredHeight() -> CGFloat{
let font = UIFont(name: "Helvetica", size: 16.0)
let label:UILabel = UILabel(frame: CGRectMake(0, 0, 200, CGFloat.max))
label.numberOfLines = 0
label.lineBreakMode = NSLineBreakMode.ByWordWrapping
label.font = font
label.text = self.text
label.sizeToFit()
return label.frame.height + //Add some space as a part of your bottom and top constraint
}
Suppose your label has top and bottom constraint as 5 and 5 respectively, them make the return statement as
return label.frame.height + 10
NOTE:- Width should be the width you want of the label. It should be according to your UITableView or UIView

UILabel resizes its SuperView?

I have the following view which contains a UILabel:
class MyView : UIView {
func viewDidLoad() {
self.autoresizingMask = UIViewAutoresizing.FlexibleHeight | UIViewAutoresizing.FlexibleWidth
bottomView = UIView(frame: CGRectMake(self.bounds.origin.x, self.bounds.origin.y + self.imageView!.bounds.size.width, self.bounds.size.width, self.bounds.size.height - self.imageView!.bounds.size.height))
// bottomView frame calculation is: (0.0, 355.0, 355.0, 130.0)
bottomView?.backgroundColor = UIColor.greenColor()
bottomView?.autoresizingMask = UIViewAutoresizing.FlexibleWidth
bottomView?.clipsToBounds = true
self.addSubview(self.bottomView!)
var descriptionRect: CGRect = CGRectInset(self.bottomView!.bounds, leftRightInset, 20/2)
let descriptionLabel = UILabel()
descriptionLabel.numberOfLines = 3
descriptionLabel.autoresizingMask = UIViewAutoresizing.FlexibleWidth
descriptionLabel.font = UIFont(name: MGFont.helvetica, size: 22)
descriptionLabel.textColor = UIColor.whiteColor()
descriptionLabel.textAlignment = NSTextAlignment.Left
descriptionLabel.backgroundColor = UIColor.blueColor()
var paragraphStyle:NSMutableParagraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineSpacing = 1.0
paragraphStyle.lineBreakMode = NSLineBreakMode.ByTruncatingTail
let attributes = [NSParagraphStyleAttributeName : paragraphStyle]
descriptionLabel.attributedText = NSAttributedString(string: previewCard.title, attributes:attributes)
bottomView?.addSubview(descriptionLabel)
descriptionLabel.bounds = descriptionRect
descriptionLabel.sizeToFit()
descriptionLabel.center = CGPointMake(bottomView!.bounds.width/2, bottomView!.bounds.height/2 - hotelNameLableHeight/2)
}
}
The height of the bottomView should always be fixed.
MyView is resized during runtime. This means that the green bottom view also increases in size.
Here is the result when the label has two and three lines:
It appears that the UILabel resizes its super view.
Note that I do not use AutoLayout.
override func layoutSubviews() {
super.layoutSubviews()
}
How can I prevent the UILabel from resizing its SuperView?
Edit: I also tried to comment bottomView?.clipsToBounds = true
Override setFrame: and setBounds: of the super view (subclass if they're plain UIViews), add breakpoints, and see the stack trace to find out what's causing them to resize.
There is no need to set the autoResizingMask on the label, just set the frame and it will get automatically centered. And of course you can set the insets for the UILabel accordingly. I've add below testing code FYI:
override func viewDidLayoutSubviews() {
addTestView(CGRectMake(0, 200, view.bounds.width, 50), labelStr: "I am a short testing label")
addTestView(CGRectMake(0, 260, view.bounds.width, 50), labelStr: "I am a very longlonglonglonglonglonglong testing label")
addTestView(CGRectMake(0, 320, view.bounds.width, 50), labelStr: "I am a very longlonglonglonglonglonglonglonglonglonglonglong testing label. Will be truncated")
}
func addTestView(frame:CGRect, labelStr: String){
let bottomView = UIView(frame:frame)
bottomView.backgroundColor = UIColor.greenColor()
bottomView.autoresizingMask = UIViewAutoresizing.FlexibleWidth
bottomView.clipsToBounds = true
view.addSubview(bottomView)
var label = UILabel(frame: bottomView.bounds)
label.textAlignment = NSTextAlignment.Left
label.numberOfLines = 0
label.text = labelStr
bottomView.addSubview(label)
}

Adjust UILabel height to text

I have some labels which I want to adjust their height to the text, this is the code I wrote for this now
func heightForView(text:String, font:UIFont, width:CGFloat) -> CGFloat{
let label:UILabel = UILabel(frame: CGRectMake(0, 0, width, CGFloat.max))
label.numberOfLines = 0
label.lineBreakMode = NSLineBreakMode.ByWordWrapping
label.font = font
label.text = text
label.sizeToFit()
return label.frame.height
}
EDIT:
The issue was not in this piece of code, so my fix is in the question itself. It might still be useful for others!
I've just put this in a playground and it works for me.
Updated for Swift 4.0
import UIKit
func heightForView(text:String, font:UIFont, width:CGFloat) -> CGFloat{
let label:UILabel = UILabel(frame: CGRectMake(0, 0, width, CGFloat.greatestFiniteMagnitude))
label.numberOfLines = 0
label.lineBreakMode = NSLineBreakMode.byWordWrapping
label.font = font
label.text = text
label.sizeToFit()
return label.frame.height
}
let font = UIFont(name: "Helvetica", size: 20.0)
var height = heightForView("This is just a load of text", font: font, width: 100.0)
Swift 3:
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
}
If you are using AutoLayout, you can adjust UILabel height by config UI only.
For iOS8 or above
Set constraint leading/trailing for your UILabel
And change the lines of UILabel from 1 to 0
For iOS7
First, you need to add contains height for UILabel
Then, modify the Relation from Equal to Greater than or Equal
Finally, change the lines of UILabel from 1 to 0
Your UILabel will automatically increase height depending on the text
In swift 4.1 and Xcode 9.4.1
Only 3 steps
Step 1)
//To calculate height for label based on text size and width
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
}
Step 2)
//Call this function
let height = heightForView(text: "This is your text", font: UIFont.systemFont(ofSize: 17), width: 300)
print(height)//Output : 41.0
Step 3)
//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)
I have the strong working solution.
in layoutSubviews:
title.frame = CGRect(x: 0, y: 0, width: bounds.width, height: 0)
title.sizeToFit()
title.frame.size = title.bounds.size
in text setter:
title.text = newValue
setNeedsLayout()
UPD.
of course with this UILabel settings:
title.lineBreakMode = .byWordWrapping
title.numberOfLines = 0
I create this extension if you want
extension UILabel {
func setSizeFont (sizeFont: CGFloat) {
self.font = UIFont(name: self.font.fontName, size: sizeFont)!
self.sizeToFit()
}
}
based on Anorak's answer, I also agree with Zorayr's concern, so I added a couple of lines to remove the UILabel and return only the CGFloat, I don't know if it helps since the original code doesn't add the UIabel, but it doesn't throw error, so I'm using the code below:
func heightForView(text:String, font:UIFont, width:CGFloat) -> CGFloat{
var currHeight:CGFloat!
let label:UILabel = UILabel(frame: CGRectMake(0, 0, width, CGFloat.max))
label.numberOfLines = 0
label.lineBreakMode = NSLineBreakMode.ByWordWrapping
label.font = font
label.text = text
label.sizeToFit()
currHeight = label.frame.height
label.removeFromSuperview()
return currHeight
}
Just by setting:
label.numberOfLines = 0
The label automatically adjusts its height based upon the amount of text entered.
The solution suggested by Anorak as a computed property in an extension for UILabel:
extension UILabel
{
var optimalHeight : CGFloat
{
get
{
let label = UILabel(frame: CGRectMake(0, 0, self.frame.width, CGFloat.max))
label.numberOfLines = 0
label.lineBreakMode = self.lineBreakMode
label.font = self.font
label.text = self.text
label.sizeToFit()
return label.frame.height
}
}
}
Usage:
self.brandModelLabel.frame.size.height = self.brandModelLabel.optimalHeight
Following on #Anorak answer, i added this extension to String and sent an inset as a parameter, because a lot of times you will need a padding to your text.
Anyway, maybe some you will find this usefull.
extension String {
func heightForWithFont(font: UIFont, width: CGFloat, insets: UIEdgeInsets) -> CGFloat {
let label:UILabel = UILabel(frame: CGRectMake(0, 0, width + insets.left + insets.right, CGFloat.max))
label.numberOfLines = 0
label.lineBreakMode = NSLineBreakMode.ByWordWrapping
label.font = font
label.text = self
label.sizeToFit()
return label.frame.height + insets.top + insets.bottom
}
}
Here is how to calculate the text height in Swift. You can then get the height from the rect and set the constraint height of the label or textView, etc.
let font = UIFont(name: "HelveticaNeue", size: 25)!
let text = "This is some really long text just to test how it works for calculating heights in swift of string sizes. What if I add a couple lines of text?"
let textString = text as NSString
let textAttributes = [NSFontAttributeName: font]
let textRect = textString.boundingRectWithSize(CGSizeMake(320, 2000), options: .UsesLineFragmentOrigin, attributes: textAttributes, context: nil)
just call this method where you need dynamic Height for label
func getHeightforController(view: AnyObject) -> CGFloat {
let tempView: UILabel = view as! UILabel
var context: NSStringDrawingContext = NSStringDrawingContext()
context.minimumScaleFactor = 0.8
var width: CGFloat = tempView.frame.size.width
width = ((UIScreen.mainScreen().bounds.width)/320)*width
let size: CGSize = tempView.text!.boundingRectWithSize(CGSizeMake(width, 2000), options:NSStringDrawingOptions.UsesLineFragmentOrigin, attributes: [NSFontAttributeName: tempView.font], context: context).size as CGSize
return size.height
}
Swift 4.0
self.messageLabel = UILabel(frame: CGRect(x: 70, y: 60, width:UIScreen.main.bounds.width - 80, height: 30)
messageLabel.text = message
messageLabel.lineBreakMode = .byWordWrapping //in versions below swift 3 (messageLabel.lineBreakMode = NSLineBreakMode.ByWordWrapping)
messageLabel.numberOfLines = 0 //To write any number of lines within a label scope
messageLabel.textAlignment = .center
messageLabel.textColor = UIColor.white
messageLabel.font = messageLabel.font.withSize(12)
messageLabel.sizeToFit()
Blockquote NSParagraphStyle.LineBreakMode, apply to entire paragraphs, not words within paragraphs.This property is in effect both during normal drawing and in cases where the font size must be reduced to fit the label’s text in its bounding box. This property is set to byTruncatingTail by default.
This link describes the storyboard way of doing the same
Swift 4.0
Instead of calculating the text/label height, I just resize the label after inserting the (dynamic) text.
Assuming that myLabel is the UILabel in question:
let myLabel = UILabel(frame: CGRect(x: 0, y: 0, width: *somewidth*, height: *placeholder, e.g. 20*))
myLabel.numberOfLines = 0
myLabel.lineBreakMode = .byWordWrapping
...
And now comes the fun part:
var myLabelText: String = "" {
didSet {
myLabel.text = myLabelText
myLabel.sizeToFit()
}
}
The Swift 4.1 extension method to calculate label height:
extension UILabel {
func heightForLabel(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
}
}
Swift 5, XCode 11 storyboard way. I think this works for iOS 9 and higher. You want for example "Description" label to get the dynamic height, follow the steps:
1) Select description label -> Go to Attributes Inspector (pencil icon), set:
Lines: 0
Line Break: Word Wrap
2) Select your UILabel from storyboard and go to Size Inspector (ruler icon),
3) Go down to "Content Compression Resistance Priority to 1 for all other UIView (lables, buttons, imageview, etc) components that are interacting with your label.
For example, I have UIImageView, Title Label, and Description Label vertically in my view. I set Content Compression Resistance Priority to UIImageView and title label to 1 and for description label to 750. This will make a description label to take as much as needed height.
You can also use sizeThatFits function.
For example:
label.sizeThatFits(superView.frame.size).height
To make label dynamic in swift , don't give height constarint and in storyboard make label number of lines 0 also give bottom constraint and this is the best way i am handling dynamic label as per their content size .

Resources