Dynamically Creating and Positioning UILabels of Different Sizes - ios

I am creating dynamic UILabels and then adding them to the view. The labels are in vertical layout. Like this:
Label 1
Label 2
Label 3
Each label can be of different height. So in order to accomodate the height I use the following code. The Label 2 uses Label 1 position and height to adjust itself on the view.
-(void) createAboutLabel
{
self.aboutLabel = [[UILabel alloc] initWithFrame:CGRectMake(self.nameLabel.frame.origin.x, self.nameLabel.frame.origin.y + self.nameLabel.frame.size.height, self.view.frame.size.width - 10, 40)];
self.aboutLabel.text = self.selectedVegetableCategory.about;
self.aboutLabel.font = [Utility getFont:#"Sinhala Sangam MN" size:14];
// [self.aboutLabel sizeToFit];
[self.aboutLabel setNumberOfLines:0];
[self.aboutLabel setLineBreakMode:UILineBreakModeWordWrap];
[self.aboutLabel sizeToFit];
[self.scrollView addSubview:self.aboutLabel];
}
I am trying to find a better way of achieving the dynamic label layout.
Any suggestions?

By passing width & font, height can be retrieved -
NSString *theText = myLabel.text;
CGFloat width = myLabel.frame.size.width ;
CGSize theSize = [theText sizeWithFont:myLabel.font constrainedToSize:CGSize(width,MAXFLOAT) lineBreakMode:UILineBreakModeWordWrap];
It will return the size from that extract height.
CGFloat myLabelHeight = theSize.height;
Hope this should help.

Related

How to programmatically sizeToFit width AND height on UILabel?

I'm programmatically creating multi-line UILabels ([label setNumberOfLines:0];).
The built-in sizeToFit method of UILabel works great for 1 line UILabels, but for multi-line text, it sets the height properly, but the width is set too small, causing longer text lines to wrap.
I don't know the label width until after the user enters their text. I want to resize the labels to fit the width of the longest line of text. And per #DonMag's comment, I also want to restrict the label to not be wider than the screen.
I tried different lineBreakMode settings but there isn't a 'nowrap' option.
I've searched SO and there are many related solutions but none that solve the problem of sizeToFit for both width and height.
Is there a way to programmatically size a multi-line UILabel to fit BOTH the width AND the height of the text?
You can do this with boundingRectWithSize...
Add your label to the view and give it a starting width constraint (doesn't really matter what value, as it will be changed).
Keep a reference to that width constraint (IBOutlet works fine if you're using IB).
Don't give it a height constraint.
When you set the text of the label, you can use this to change its width:
// get the font of the label
UIFont *theFont = _theLabel.font;
// get the text of the label
NSString *theString = _theLabel.text;
// calculate the bounding rect, limiting the width to the width of the view
CGRect r = [theString boundingRectWithSize:CGSizeMake(self.view.frame.size.width, CGFLOAT_MAX)
options:(NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading)
attributes:#{NSFontAttributeName: theFont}
context:nil];
// change the constant of the constraint to the calculated width
_theWidthConstraint.constant = ceil(r.size.width);
// NOTE: If you are *not* using auto-layout,
// this same calculation can be used to explicitly set
// the .frame of the label.
Edit:
As per the OP's requirement, a complete, runnable example -- using code only, no storyboards / IB -- can be found here: https://github.com/DonMag/MultilineLabelFitWidth
Edit 2:
GitHub project updated... now includes examples for both manual frame setting and auto layout / constraints.
With some more experimentation, I found something that does the trick that I have not seen in SO (yet...). In general it works like this:
Find the longest text line
Set numberOfLines to 1 (temporarily)
Set label text to longest text line
Call label.sizeToFit (sets label width for longest line)
Set numberOfLines to 0 (multi-line)
Set label text to full multi-line text
Call label.sizeToFit (sets label height for all lines)
Voila! Now your UILabel is sized to fit your multi-line text.
Here is an example (demo project on GitHub: UILabelSizeToFitDemo):
- (UILabel *)label = nil;
- (void)updateLabel:(NSString *)notes {
// close to the "sticky" notes color
UIColor *bananaColor = [ViewController colorWithHexString:#"#FFFC79"];
if (_label == nil) {
_label = [[UILabel alloc] init];
_label.numberOfLines = 0;
_label.textColor = UIColor.blackColor;
[_label setBackgroundColor:[bananaColor colorWithAlphaComponent:0.9f]];
_label.textAlignment = NSTextAlignmentLeft;
[self.view addSubview:_label];
}
// make font size based on screen size
CGFloat screenWidth = [UIScreen mainScreen].bounds.size.width;
CGFloat screenHeight = [UIScreen mainScreen].bounds.size.height;
CGFloat fontSize = MIN(screenWidth,screenHeight) / 12;
[_label setFont:[UIFont systemFontOfSize:fontSize]];
// split lines
NSArray *lines = [notes componentsSeparatedByString:#"\n"];
NSString *longestLine = lines[0]; // prime it with 1st line
// fill a temp UILabel with each line to find the longest line
for (int i = 0; i < lines.count; i++) {
NSString *line = (NSString *)lines[i];
if (longestLine == nil || line.length > longestLine.length) {
longestLine = line;
}
}
// force UILabel to fit the largest line
[_label setNumberOfLines:1];
[_label setText:longestLine];
[_label sizeToFit];
// make sure it doesn't go off the screen
if (_label.frame.size.width > screenWidth) {
CGRect frame = _label.frame;
frame.size.width = screenWidth - 20;
_label.frame = frame;
}
// now fill with the actual notes (this saves the previous width)
[_label setNumberOfLines:0];
[_label setText:notes];
[_label sizeToFit];
// center the label in my view
CGPoint center = CGPointMake(self.view.bounds.size.width / 2, self.view.bounds.size.height / 2);
[_label setCenter:center];
}
UPDATE: Here is an alternate complete solution, using the boundinRectWithSize from the code snippet by #DonMag:
-(void)updateLabel:(NSString *)notes {
// close to the "sticky" notes color
UIColor *bananaColor = [ViewController colorWithHexString:#"#FFFC79"];
if (_label == nil) {
_label = [[UILabel alloc] init];
_label.numberOfLines = 0;
_label.textColor = UIColor.blackColor;
_label.backgroundColor = [bananaColor colorWithAlphaComponent:0.9f];
_label.textAlignment = NSTextAlignmentLeft;
[self.view addSubview:_label];
}
// set new text
_label.text = notes;
// make font size based on screen size
CGFloat screenWidth = [UIScreen mainScreen].bounds.size.width;
CGFloat screenHeight = [UIScreen mainScreen].bounds.size.height;
CGFloat fontSize = MIN(screenWidth,screenHeight) / 12;
[_label setFont:[UIFont systemFontOfSize:fontSize]];
// calculate the bounding rect, limiting the width to the width of the view
CGRect frame = [notes boundingRectWithSize:CGSizeMake(self.view.frame.size.width, CGFLOAT_MAX)
options:(NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading)
attributes:#{NSFontAttributeName: _label.font}
context:nil];
// set frame and then use sizeToFit
[_label setFrame:frame];
[_label sizeToFit];
// center the label in my view
CGPoint center = CGPointMake(self.view.frame.size.width / 2, self.view.frame.size.height / 2);
[_label setCenter:center];
}

UILabel.frame.size.height shows less height than actual value

I am creating a UILabel with dynamic height by calculating the length of the text with this code:
UILabel *bankFullName = [[UILabel alloc] init];
[bankFullName setLineBreakMode:NSLineBreakByWordWrapping];
[bankFullName setMinimumScaleFactor:1];
[bankFullName setNumberOfLines:0];
[bankFullName setFont:[UIFont systemFontOfSize:13]];
CGSize constraint1 = CGSizeMake(240.0f, 20000.0f);
CGSize size1 = [[bankDetail valueForKey:#"FullName"] sizeWithFont:[UIFont systemFontOfSize:13] constrainedToSize:constraint1 lineBreakMode:NSLineBreakByWordWrapping];
bankFullName.text = [bankDetail valueForKey:#"FullName"];
CGRect fullnameFrame = CGRectMake(15.0, 15.0, 240.0, size1.height);
bankFullName.frame = fullnameFrame;
Now I don't have any issue with the code and the label is perfect.
The problem is when I log bankFullName.frame.size.height, the height is not right. If the label has two lines, it gives the height of the label having one line and so on.
Any idea on how to get the accurate height of the label or if something I'm doing is wrong?
Thanks
Just log the height of label in this method as -[UIView layoutSubviews] get called when the size of the any view changes:-
-(void)layoutSubviews
{
[super layoutSubviews];
NSLog(#""... // log your label height to get the exact value.
}
Do not use sizeWithFont: to change the height of your label.
Use [bankFullName sizeToFit] instead. And don't forget to set the maximum number of lines to more than one.
You should use sizeThatFits: to get correct size for UILabel, because sizeWithFont and boundingRectWithSize:options:attributes:context don't include UILabel padding.
CGFloat height = [self.bankFullName sizeThatFits:CGSizeMake(240, MAXFLOAT)].height;
self.bankFullName.frame = CGRectMake(15, 15, 240, height);

Adding UILabels to UIView dynamically

I have a web service that brings some text data back. The idea is to extract the text and create UILabels for the text to show on screen. I however, do not know two things:
How many labels are needed
The length of the text
Due to having no prior knowledge of these things I need a way to create some labels that have the right length for the text it contains.
I've managed to store the data into some objects that are in an array and then iterate through them creating the labels.. something like this:
for (BBItemAttributes *attribute in self.item.productAttributes){
UILabel *label = [[UILabel alloc]initWithFrame:CGRectMake(100, 100, 50, 10)];
label.text = attribute.displayTemplate;
[self.scrollView addSubview:label];
}
Obviously the problem here is due to creating the UILabels in code and having each one a hard coded CGRect size they are all onto of each other and sometimes don't fit in their respective boxes due to text being too long.
I need a way to line the labels up all on the same X axis point and on different Y axis points, so that they sit next to each other a certain space apart.
Is there a better way to do this?
You need to do something like this;
CGFloat yOrigin = 100;
CGFloat fixedSpace = 10;
for (BBItemAttributes *attribute in self.item.productAttributes)
{
UILabel *label = [[UILabel alloc]initWithFrame:CGRectMake(100, yOrigin, 50, 10)];
label.text = attribute.displayTemplate;
label.numberOfLines = 0;
[label sizeToFit]; // Resizes label to fit the text.
[self.scrollView addSubview:label];
yOrigin += label.frame.size.height + fixedSpace;
}
Try this. it will help you.
float yAxis = 0;
for (int i = 0; i< 50; i++)//for (BBItemAttributes *attribute in self.item.productAttributes){
{
CGSize size;
UIFont *font = [UIFont systemFontOfSize:15.0]; // Set Your Font
//Your String = attribute.displayTemplate
if (ios7)//Condition to check if ios7
{
//iOS 7
CGRect frame = [#"Your String" boundingRectWithSize:CGSizeMake(50, CGFLOAT_MAX)
options:NSStringDrawingUsesLineFragmentOrigin
attributes:#{NSFontAttributeName:font}
context:nil];
size = CGSizeMake(frame.size.width, frame.size.height+1);
}
else
{
//iOS 6.0
size = [#"Your String" sizeWithFont:font constrainedToSize:CGSizeMake(50, CGFLOAT_MAX) lineBreakMode:NSLineBreakByWordWrapping];
}
UILabel *label = [[UILabel alloc]initWithFrame:CGRectMake(100, yAxis, 50, size.height)];
label.text = #"Your String";//attribute.displayTemplate
label.numberOfLines = 0;
label.tag = i; // Set tag if you want to access in future. :)
[self.scrollView addSubview:label];
// Increase yAxis
yAxis = yAxis + size.height + 10;//10 is extra space if you want between two label
}
// Do Not forget to set Contentsize of your ScrollView.
self.scrollView.contentSize = CGSizeMake(320, yAxis + 20);
If your showing a data structure that is a like a list of labels, I would suggest that you use UITableViewController for showing the list.It has the built in facility and properties that make it easy to display a list of objects.
Now the answer to your first Question.You can get the number of labels by determining the number of objects that are in your array. Something like this :
NSInteger *noOfLabels = yourArray.count;
As far as the length of the text is concerned, I suggest not keeping it very lengthy because Labels are not good for lengthy texts. You can however, specify the number of a lines for a particular label in case if it has lengthy text.This will make the text appear in two lines. Though you will have to adjust width and height accordingly.
You can get the text length by doing something similar to this :
NSString *text;
NSInteger *i = (NSInteger*)[text length];
Hope this helps.

UILabel text alignment issues in iphone?

I am getting JSON response from server and set that contents to UILabel and sometimes the text will be one line and sometimes the text will be 10 lines.So i display this UILabel inside the UIScrollView. But i got the empty space in UILabel
This is my screenshots..
Currently i am using storyboard and auto layout
So i want to avoid these empty space in UILabel. Please give me suggestions for this alignment issues.??
Code for changing UILabel height but this is not working for me
CGRect textRect = [strDetailsFinal boundingRectWithSize:CGSizeMake(296, FLT_MAX)
options:NSStringDrawingUsesLineFragmentOrigin
attributes:#{NSFontAttributeName: labDetails.font}
context:nil];
CGSize size = textRect.size;
CGRect newFrame = labDetails.frame;
newFrame.size.height = size.height;
labDetails.frame = CGRectMake(160, 200, 300, newFrame.size.height);
May this help you
NSDictionary *attributesDic= [NSDictionary dictionaryWithObjectsAndKeys:
[UIFont fontWithName:#"FontName" size:15], NSFontAttributeName,nil];
CGRect frame = [label.text boundingRectWithSize:CGSizeMake(263, 2000.0)
options:NSStringDrawingUsesLineFragmentOrigin attributes:attributesDic context:nil];
CGSize size = frame.size;
and its possible answer Dynamically resize label in iOS 7 or you can try this https://gist.github.com/danielphillips/1005520
Use [labDetails sizeToFit]. If you have size constraints then use [sizeThatFits:CGSizeMake(WidthConstraint, HeightConstraint)] then use the result of that to set your label frame.
why don't you use UIButton instead of UILabel. UIButton has property to align the text vertically, while UILabel doesn't.
Use UIButton with userInteractionEnabled = NO. Check my this reply here: Vertically align UILabel
Remember one thumb rule - You should not set frame when using autolayout - You are going against Autolayout basic concept - Instead try to autolayout constraints programatically in your case
-(void)awakeFromNib{
[super awakeFromNib];
for(NSLayoutConstraint *cellConstraint in self.constraints){
[self removeConstraint:cellConstraint];
id firstItem = cellConstraint.firstItem == self ? self.contentView : cellConstraint.firstItem;
id seccondItem = cellConstraint.secondItem == self ? self.contentView : cellConstraint.secondItem;
NSLayoutConstraint* contentViewConstraint =
[NSLayoutConstraint constraintWithItem:firstItem
attribute:cellConstraint.firstAttribute
relatedBy:cellConstraint.relation
toItem:seccondItem
attribute:cellConstraint.secondAttribute
multiplier:cellConstraint.multiplier
constant:cellConstraint.constant];
[self.contentView addConstraint:contentViewConstraint];
}
}
You have taken a fix size UILable rather you have to use a dynamic size UILable.
Before that you need to calculate the size of text/string
#define FONT_SIZE 15.0f
#define LABEL_CONTENT_WIDTH 320.0f
#define LABEL_CONTENT_MARGIN 10.0f
NSString *text = #"YOUR TEXT HERE";
CGSize constraint = CGSizeMake(LABEL_CONTENT_WIDTH - (LABEL_CONTENT_MARGIN * 2), 20000.0f);
CGSize size = [text sizeWithFont:[UIFont systemFontOfSize:FONT_SIZE] constrainedToSize:constraint lineBreakMode:NSLineBreakByWordWrapping];
CGFloat height = MAX(size.height, 44.0f);
after calculating the size, apply them to your label like below
[labDetails setFrame:CGRectMake(labDetails.frame.origin.x, labDetails.frame.origin.y, labDetails.frame.size.width, MAX(height, 44.0f))];
And If you are using autolayout option then please check here: Link
Use Upper label frame's origin and size, and based on that set your detail labels frame, Use sizeToFit property of label.
Suppose 582,618 is your title label,
Like,
labDetails.frame=CGRectMake(5, title.frame.origin.x+title.frame.size.height+5, 300, 100);
[labDetails sizeToFit];

Why is UIFont sizeWithFont including blank space in its calculation?

I am setting a UILabels frame based on what is returned by UIFont sizeWithFont but for whatever reason when i use a custom font, the values that are returned include some padding as seen below.
When i use boldSystemFontOfSize the text is vertically aligned in the middle (which is what i want), but when i use fontWithName i end up with padding under the text. Any reason why sizeWithFont is adding in the padding?
Heres my code...
CGRect frameLabel = label.frame;
CGSize sizeLabel = [label.text sizeWithFont:label.font];
frameLabel.size.width = sizeLabel.width;
frameLabel.size.height = sizeLabel.height;
[label setBackgroundColor:[UIColor redColor]];
** Edit **
I can calculate the top and bottom padding using this code and adjust the labels frame.origin.y to vertically center my label where it needs to be
float topPadding = [label.font ascender] - [label.font capHeight];
float bottomPadding = [label.font lineHeight] - [label.font ascender];
Font is the only possible cause of this padding, but if you only need one-line labels, don't waste your time editing the font, just reduce the label's height by those few pixels after setting a proper frame by doing something like this:
label.frame = CGRectInset(label.frame, 0, bottomPadding);
Also, instead of:
CGRect frameLabel = label.frame;
CGSize sizeLabel = [label.text sizeWithFont:label.font];
frameLabel.size.width = sizeLabel.width;
frameLabel.size.height = sizeLabel.height;
You can just call:
[label sizeToFit];

Resources