How to programmatically add a UILabel with variable height and layout ios
Well, finally I've found the error, as I stated in the "UPDATE 2", it was very strange that it was working on an UILabel
added via IB, but not with those added dynamically.
So, I looked again to my screen on IB, and found that I have an height constraint with "equal" relation, when I changed it to "greater or equal than" it all started to work!!!! Why I did not notice that the constraint was not "greater or equal than"? Because the view with the constraint was getting larger and larger as I was adding views with labels inside. Is that a bug?
Well, to summarize if someone find this useful. "How to add UILabel
s multi-line which adapts to its content?"
In iOS 6 versions it was a bug with "preferredMaxLayoutWidth
", not getting the width once it was "layouted", and as a result of that programmers must set it via code (the best solution is doing it dynamically, an example of that I put it on my above question).
Fortunately on iOS7 and later, this has been solved, and you can rely only with constraints.
Adding via IB
- Add the
UILabel
, and set "number of lines" to '0' (this will make theUILabel
use as many lines as it needs). - Add the constraints, you need at least 3 constraints (pin to top or bottom, and both sides), the fourth will be height with a relation "greater or equal than".
- Be sure than the view container has enough room, or also has a height constraint with a relation "greater or equal than".
- Add the
Adding via code (just paste the code posted above):
UILabel *miLabel = [[UILabel alloc] init]; // Initialitze the UILabel miLabel.translatesAutoresizingMaskIntoConstraints = NO; // Mandatory to disable old way of positioning miLabel.backgroundColor = [UIColor redColor]; miLabel.numberOfLines = 0; // Mandatory to allow multiline behaviour miLabel.text = @"ñkhnaergñsdbmnñgwn"; // big test to test [self.viewLabels addSubview:miLabel]; // Add subview to the parent view [self.viewLabels addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(10)-[label]-(10)-|" options:0 metrics:nil views:@{@"label":miLabel}]]; // horizontal constraint [self.viewLabels addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(10)-[label]" options:0 metrics:nil views:@{@"label":miLabel}]]; UIView *pre = miLabel; // Adding a second label to test the separation miLabel = [[UILabel alloc] init]; miLabel.translatesAutoresizingMaskIntoConstraints = NO; miLabel.backgroundColor = [UIColor greenColor]; miLabel.numberOfLines = 0; miLabel.text = @"ñkhnaermgñlkafmbnoetñnngñsdbmnñgwn"; [self.viewLabels addSubview:miLabel]; [self.viewLabels addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(10)-[label]-(10)-|" options:0 metrics:nil views:@{@"label":miLabel,@"boton":miBoton}]]; [self.viewLabels addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[previous]-(10)-[label]" options:0 metrics:nil views:@{@"label":miLabel,@"previous":pre}]];
Thanks to those who has tried to help me!! This site is amazing :)
raululm
Updated on June 05, 2022Comments
-
raululm almost 2 years
I have been searching for the whole day, but could not make it work.
I have a
UIView
with two labels, on above the other, on the left side, and one button on the right side. I added the constraints to letautolayout
resize views accordingly. Everything was working perfectly when I had set one constraint for the height of theUIView
(and no one constraint for the height of the twoUILabel
), but as the content of the lowerUILabel
will vary, I removed that constraint and set two constraints for theUILabel
, one fixed for the upperUILabel
and one with relation "greater than or equal" for the lowerUILabel
, and other one to fix the distance between the lowerUILabel
and theUIView
.It looks like auto layout is not capable of calculate the
intrinsicContentSize
of the lowerUILabel
, because it never increases its height above 10px, despite the content of the lowerUILabel
.UIView *miView = [[UIView alloc] init]; UILabel *miLabel = [[UILabel alloc] init]; [miLabel setTranslatesAutoresizingMaskIntoConstraints:NO]; [miDetailsLabel setNumberOfLines:1]; [miDetailsLabel setText:@"Just one line."]; [miView addSubview:miLabel]; UILabel *miDetailsLabel = [[UILabel alloc] init]; [miDetailsLabel setTranslatesAutoresizingMaskIntoConstraints:NO]; [miDetailsLabel setNumberOfLines:0]; [miDetailsLabel setText:@"Enough text to show 3 lines on an IP4, except first of three UILabels on my test code, with no content"]; [miView addSubview:miDetailsLabel]; UIButton *miButton = [[UIButton alloc] init]; [miButton setTranslatesAutoresizingMaskIntoConstraints:NO]; [miView addSubview:miButton]; NSArray *constraint_inner_h = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(0)-[label]-(10)-[button(52)]-(0)-|" options:0 metrics:nil views:@{@"label":miLabel, @"button":miButton}]; NSArray *constraint_inner2_h = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(0)-[details]-(10)-[button]-(0)-|" options:0 metrics:nil views:@{@"details":miDetailsLabel, @"button":miButton}]; NSArray *constraint_label_v = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(0)-[label(18)]-(2)-[details(>=10)]-(0)-|" options:0 metrics:nil views:@{@"label":miLabel, @"details":miDetailsLabel}]; NSArray *constraint_button_v = [NSLayoutConstraint constraintsWithVisualFormat:@"V:[button(22)]" options:0 metrics:nil views:@{@"button":miButton}]; [miView addConstraint:[NSLayoutConstraint constraintWithItem:miButton attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:miView attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0.0]]; [miView addConstraints:constraint_inner_h]; [miView addConstraints:constraint_inner2_h]; [miView addConstraints:constraint_label_v]; [miView addConstraints:constraint_button_v];
I ve put a reduced version of the code.
any ideas of what I am missing?
Thanks
UPDATE : Thanks to Matt advice, I've tried this solution to set the proper value to
preferredMawLayoutWidth:
- (void)viewDidLayoutSubviews { [super viewDidLayoutSubviews]; NSArray *allKeys = [dictOpcionesTickets allKeys]; for (NSString *key in allKeys) { NSArray *tmpArray = [dictOpcionesTickets objectForKey:key]; if (![[tmpArray objectAtIndex:0] isEqual:@""]) { UILabel *tmpLabel = [tmpArray objectAtIndex:3]; tmpLabel.preferredMaxLayoutWidth = tmpLabel.frame.size.width; NSLog(@"width: %f",tmpLabel.frame.size.width); } } [self.view layoutIfNeeded]; }
To explain the code, as I said, I'm doing it dynamically, so I've created a dictionary with an array with the references to my
UILabel
(among other interesting information). When I run the code, I get the next log (with the code parsing 3UILabel
, first label with no content):2014-12-28 20:40:42.898 myapp[5419:60b] width: 0.000000 2014-12-28 20:40:42.912 myapp[5419:60b] width: 0.000000 2014-12-28 20:40:43.078 myapp[5419:60b] width: 229.000000 2014-12-28 20:40:43.080 myapp[5419:60b] width: 229.000000 2014-12-28 20:40:43.326 myapp[5419:60b] width: 229.000000 2014-12-28 20:40:43.327 myapp[5419:60b] width: 229.000000
But I'm still getting the same result...the UIView is still showing a height equals to the minimum height set by the constraints, not showing the content of the
UILabel
.UPDATE 2 Still fooling around.
I've tested my initial code but simplified on a fresh project in xcode 6, running on an actual iPhone 4 with iOS7, and it worked perfectly without setting
preferredMaxLayoutWidth
or subclassingUILabel
, and even without callinglayoutIfNeeded
on parent view. But when it comes to the real project (I think it was originally builded in xcode 4 or 5) it does not work:- (void)addLabelsDinamically { self.labelFixed.text = @"Below this IB label goes others added dinamically..."; UIButton *miBoton = [[UIButton alloc] init]; miBoton.translatesAutoresizingMaskIntoConstraints = NO; [miBoton setTitle:@"Hola" forState:UIControlStateNormal]; [self.viewLabels addSubview:miBoton]; [self.viewLabels addConstraint:[NSLayoutConstraint constraintWithItem:miBoton attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.viewLabels attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0.0]]; [self.viewLabels addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[boton(22)]" options:0 metrics:nil views:@{@"boton":miBoton}]]; [self.viewLabels addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:[boton(40)]" options:0 metrics:nil views:@{@"boton":miBoton}]]; UILabel *miLabel = [[UILabel alloc] init]; miLabel.translatesAutoresizingMaskIntoConstraints = NO; miLabel.backgroundColor = [UIColor redColor]; miLabel.numberOfLines = 0; miLabel.text = @"ñkhnaermgñlkafmbñlkadnlñejtnhalrmvlkamnñnañorenoetñnngñsdbmnñgwn"; [self.viewLabels addSubview:miLabel]; [self.viewLabels addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(8)-[label]-(10)-[boton]-(8)-|" options:0 metrics:nil views:@{@"label":miLabel,@"boton":miBoton}]]; [self.viewLabels addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[previous]-(8)-[label]" options:0 metrics:nil views:@{@"label":miLabel,@"previous":self.labelFixed}]]; UIView *pre = miLabel; miLabel = [[UILabel alloc] init]; miLabel.translatesAutoresizingMaskIntoConstraints = NO; miLabel.backgroundColor = [UIColor greenColor]; miLabel.numberOfLines = 0; miLabel.text = @"ñkhnaermgñlkafmbñlkadnlñejtnhalrmvlkamnñnañorenoetñnngñsdbmnñgwn"; [self.viewLabels addSubview:miLabel]; [self.viewLabels addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(8)-[label]-(10)-[boton]-(8)-|" options:0 metrics:nil views:@{@"label":miLabel,@"boton":miBoton}]]; [self.viewLabels addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[previous]-(8)-[label]" options:0 metrics:nil views:@{@"label":miLabel,@"previous":pre}]]; }
Ah, on the same screen I have one
UILabel
added on IB, withnumberOfLines
set to '0', vertical constraint "greater or equal than" and horizontal constraint set in a similar way (but both constraints set on IB)...and it works perfectly...this is driving me nuts!! any help???