How to programmatically add a UILabel with variable height and layout ios

12,615

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 UILabels 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.

  1. Adding via IB

    • Add the UILabel, and set "number of lines" to '0' (this will make the UILabel 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".
  2. 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 :)

Share:
12,615
raululm
Author by

raululm

Updated on June 05, 2022

Comments

  • raululm
    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 let autolayout resize views accordingly. Everything was working perfectly when I had set one constraint for the height of the UIView (and no one constraint for the height of the two UILabel), but as the content of the lower UILabel will vary, I removed that constraint and set two constraints for the UILabel, one fixed for the upper UILabel and one with relation "greater than or equal" for the lower UILabel, and other one to fix the distance between the lower UILabel and the UIView.

    It looks like auto layout is not capable of calculate the intrinsicContentSize of the lower UILabel, because it never increases its height above 10px, despite the content of the lower UILabel.

    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 3 UILabel, 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 subclassing UILabel, and even without calling layoutIfNeeded 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, with numberOfLines 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???