Dynamically adding multiline label in a view - ios

I am dynamically creating UILabel in my app to display a list of directions for a recipe. The labels populate correctly displaying all the items one after another.
The problem is when the text goes on next line of the label, it is overlapped with the next label.
I have set numberOfLines to 0 and also set lineBreakMode to NSLineBreakByWordWrapping. This helps the label to display text on multiple lines.
I have tried to adjust the height of the label, as you will see in the code, but it doesn't work.
How do I prevent the overlapping of labels due to multiline text in label?
Here is the code for populating the labels with multilines:
//add all the directions to the uiview
for (int i = 0; i < self.recipe.directions.count; i++)
{
UILabel *label = [[UILabel alloc] initWithFrame: CGRectMake(0,(i+1)*25,280,25)];
label.lineBreakMode = NSLineBreakByWordWrapping; //multiple lines in a label
label.numberOfLines = 0;
label.text =[NSString stringWithFormat: #"%#", self.recipe.directions[i]];
[label sizeToFit]; // resize the width and height to fit the text
NSLog(#"Actual height is: %f", label.frame.size.height); // Use this for spacing any further elements
CGSize expectedLabelSize = [label.text sizeWithFont:label.font
constrainedToSize:label.frame.size
lineBreakMode:NSLineBreakByWordWrapping];
//adjust the label the the new height.
CGRect newFrame = label.frame;
newFrame.size.height = expectedLabelSize.height;
label.frame = newFrame;
[self.directionsView addSubview:label];
}

Instead of initialising your label with an y position of (i+1)*25, you should store your last label's bottom position
CGFloat lastLabelBottomCoordinate = 25;
CGFloat spaceBetweenLines = 10;
for (int i = 0; i < 10; i++)
{
UILabel *label = [[UILabel alloc] initWithFrame: CGRectMake(0, lastLabelBottomCoordinate + spaceBetweenLines,280,25)];
label.lineBreakMode = NSLineBreakByWordWrapping; //multiple lines in a label
label.numberOfLines = 0;
label.text =[NSString stringWithFormat: #"This is a very long text to see if the text have more than 2 lines"];
[label sizeToFit]; // resize the width and height to fit the text
NSLog(#"Actual height is: %f", label.frame.size.height); // Use this for spacing any further elements
CGSize expectedLabelSize = [label.text boundingRectWithSize:CGSizeMake(label.frame.size.width, MAXFLOAT)
options:NSStringDrawingUsesLineFragmentOrigin
attributes:#{NSFontAttributeName : label.font}
context:nil].size;
//adjust the label the the new height.
CGRect newFrame = label.frame;
newFrame.size.height = expectedLabelSize.height;
label.frame = newFrame;
lastLabelBottomCoordinate = label.frame.origin.y + label.frame.size.height;
[self.view addSubview:label];
}
This is how it looks:

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];
}

Remove space from top of text in UILabel

There is a UILabel in UITableView cell. If label have long text then it adds some space on top from text in label. After scrolling this space is automatically remove. I want to remove this space initially. What should i do. I have adjusting height of label using this code:
- (void) adjustHeightOfLbl : (UILabel *) lbl : (NSIndexPath *)indexPath {
lbl.numberOfLines = 0;
NSAttributedString *attributedText =
[[NSAttributedString alloc] initWithString:(lbl.text?:#"")
attributes:#{NSFontAttributeName: lbl.font}];
CGRect rect = [attributedText boundingRectWithSize:(CGSize){lbl.frame.size.width, CGFLOAT_MAX}
options:NSStringDrawingUsesLineFragmentOrigin
context:nil];
CGSize size = rect.size;
CGRect newFrame = lbl.frame;
newFrame.size.height = size.height;
lbl.frame = newFrame;
}

how to do truncation of text in UILabel

I have problem with truncation of text in UILabel even I have set lineBreakMode = NSLineBreakByWordWrapping perfectly.
Here is my code snippet :
lblSelectedText = [[UILabel alloc] init];
lblSelectedText.numberOfLines = 0;
lblSelectedText.lineBreakMode = NSLineBreakByWordWrapping;
lblSelectedText.font = [UIFont fontWithName:#"MyriadPro-Regular" size:(IS_IPAD_PRO?13.0:9.0)];
lblSelectedText.textAlignment = NSTextAlignmentCenter;
lblSelectedText.textColor = [UIColor lightGrayColor];
lblSelectedText.text = strKey; // Here text will be dynamic
CGFloat width = 150;
CGSize strSize = [self findHeightForText:strKey havingWidth:width andFont:lblSelectedText.font];
lblSelectedText.frame = CGRectMake(12, 10, CGRectGetWidth(aContainerController.view.frame)-20, strSize.height+15);
- (CGSize)findHeightForText:(NSString *)text havingWidth:(CGFloat)widthValue andFont:(UIFont *)font
{
CGSize size = CGSizeZero;
if (text)
{
if (IS_ENGLISH) {
CGRect frame = [text boundingRectWithSize:CGSizeMake(widthValue, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin attributes:#{ NSFontAttributeName:font } context:nil];
size = CGSizeMake(frame.size.width, frame.size.height + 10);
}
else {
CGRect frame = [text boundingRectWithSize:CGSizeMake(widthValue, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin attributes:#{ NSFontAttributeName:font } context:nil];
size = CGSizeMake(frame.size.width, frame.size.height + 1);
}
}
return size;
}
Please note that these labels are in collection view cell. Here are some reference images.
If anyone have solution of this problem please share with me...
Thanks.
As you have mentioned in comment that some characters goes in to next line that means there is not enough width to fill every letter in single line thats why it is going to second line as you set number of line to 0(i.e. multiple lines). So there is nothing is wrong in it. It is normal behavior according to your setup made in code.
now if you want that it not goes to second line then set numberOfLines property to 1.
Another option is you can set minimunScaleFactor from 0 to 1 to resize your font to fit in available width.
You can do it something like,
label.adjustsFontSizeToFitWidth = YES;
label.minimumScaleFactor = 0.5f;
So, It will reduce font size to half of the actual to fit it in availabel width.
And once try by changing linebreakmode to NSLineBreakByTruncatingTail also instead of NSLineBreakByWordWrapping.
Check this:-
label.minimumScaleFactor = 0.5f;
label.lineBreakMode = NSLineBreakByTruncatingMiddle;
[label sizeToFit];

Set UILabel font size according to its frame size on IOS8?

I found a lot of solution with the property “adjustsFontSizeToFitWidth" , but none of these solutions works on IOS8.
I develop a responsive app and the label font size has to change according to the screen width...
I have :
label = [[UILabel alloc] init];
label.numberOfLines = 1;
label.adjustsFontSizeToFitWidth = YES;
label.font = [UIFont fontWithName:FONT_DIGITAL size:60];
[label setMinimumScaleFactor:8.0/label.font.pointSize];
label.textAlignment = NSTextAlignmentCenter;
label.frame = CGRectMake(0, 0, self.width, self.height);
label.text = #"TEXT";
[self addSubview:valueLabel];
Someone has a solution?
You should set your label's auto shrink property as shown in below image.
Hope this helps.
Following answer is a hack (not the best solution), but it works, so if you want, you can try:
- (void) sizeLabel: (UILabel *) label toRect: (CGRect) labelRect {
// Set the frame of the label to the targeted rectangle
label.frame = labelRect;
// Try all font sizes from largest to smallest font size
CGFloat fontSize = 16.0f;
CGFloat minFontSize = 5.0f;
// Fit label width wize
CGSize constraintSize = CGSizeMake(label.frame.size.width, MAXFLOAT);
do {
// Set current font size
label.font = [UIFont fontWithName:label.font.fontName size:fontSize];
// Find label size for current font size
CGRect textRect = [[label text] boundingRectWithSize:constraintSize
options:NSStringDrawingUsesLineFragmentOrigin
attributes:#{NSFontAttributeName:label.font}
context:nil];
CGSize labelSize = textRect.size;
// Done, if created label is within target size
if( labelSize.height <= label.frame.size.height )
break;
// Decrease the font size and try again
fontSize -= 1.0f;
} while (fontSize > minFontSize);
}

how create uilabel's dynamically from an NSMutableArray?

NSMutableArray *items // contains 15 items
I need to put one down label from another i try something like this but not work
int count=20;
for(int i = 0; i < [items count]; i++){
UILabel *label = [[UILabel alloc] initWithFrame: CGRectMake(0,0,0,count)];
label.text = #"text"; //etc...
count+=20;
[_scroll addSubview:label];
}
What can i do thanks
You need to set the frame properly.
int count=20;
for(int i = 0; i < [items count]; i++){
UILabel *label = [[UILabel alloc] initWithFrame: CGRectMake(0,count,0,0)];
label.text = #"text"; //etc...
[label sizeToFit]; // resize the width and height to fit the text
count+=20;
[_scroll addSubview:label];
}
As suggested by rmaddy...adding a new line to adjust the height of the label as well, assumed that you have an NSMutableArray object 'items' containing strings.
float previousLabelHeight = 0.0;
for(int i = 0; i < [items count]; i++){
CGSize theSize = [[items objectAtIndex: i] sizeWithFont:[UIFont systemFontOfSize:17.0] constrainedToSize:CGSizeMake(320, FLT_MAX) lineBreakMode:UILineBreakModeWordWrap]; //can adjust width from 320 to whatever you want and system font as well
float newLabelHeight = previousLabelHeight + theSize.height;
UILabel *label = [[UILabel alloc] initWithFrame: CGRectMake(0,newLabelHeight,0,0)];
label.text = [items objectAtIndex: i];
[label sizeToFit]; // resize the width and height to fit the text
previousLabelHeight = newLabelHeight + 5 //adding 5 for padding
[_scroll addSubview:label];
}
Cheers,
Happy Coding.
I think you're trying to change the frame's Y value, but the last parameter of CGRectMake() is the rect's height. You want the second parameter.
Here is the Swift version to add labels dynamically from array..
var previousLabelHeight: CGFloat = 0.0;
for dict in items {
let text: String = "Some text to display in the UILabel"
let size = heightNeededForText(text as NSString, withFont: UIFont.systemFontOfSize(15.0), width: scrollView.frame.size.width - 20, lineBreakMode: NSLineBreakMode.ByWordWrapping)
let newLabelHeight = previousLabelHeight + size;
let label = UILabel(frame: CGRectMake(0, newLabelHeight, 0, 0))
label.text = text
label.sizeToFit() // resize the width and height to fit the text
previousLabelHeight = newLabelHeight + 5 //adding 5 for padding
scroll.addSubview(label)
}
As sizeWithFont: ConstraintedToSize is deprecated from ios 7.0,
we have to use boundingRectWithSize method from NSString....
func heightNeededForText(text: NSString, withFont font: UIFont, width: CGFloat, lineBreakMode:NSLineBreakMode) -> CGFloat {
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineBreakMode = lineBreakMode
let size: CGSize = text.boundingRectWithSize(CGSizeMake(width, CGFloat.max), options: [.UsesLineFragmentOrigin, .UsesFontLeading], attributes: [ NSFontAttributeName: font, NSParagraphStyleAttributeName: paragraphStyle], context: nil).size//text boundingRectWithSize:CGSizeMake(width, CGFLOAT_MA
return ceil(size.height);
}

Resources