25
votes

I created a sample project to reproduce this.

I've a xib file with an UILabel having a fixed top, leading and trailing constraint. I added a minHeight constraint and set the number of lines to 0.

I set the preferredMaxLayoutWidth to Automatic (checked in the xib file).

In viewDidLoad, I've this:

self.myLabel.layer.borderColor = [[UIColor redColor] CGColor];
self.myLabel.layer.borderWidth = 2.0f;
self.myLabel.text = @"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";

And when i run the simulator on iPhone 6 or 6+, I get this:

enter image description here

I've no idea where that top and bottom padding is coming from and it's proportional to the amount of characters in the UILabel is showing.

Is there some magic setting I forgot ? It runs fine on iPhone 4" devices.

Now if I don't set the preferredMaxLayoutWidth, it doesn't have the extra padding but this breaks my multi-lines in the UILabel. It cuts off the text. I did not use Size-Class.

Edit:

So I changed few things on my sample project to match the situation on my real project. I've added a tableView (with top, leading, bottom and trailing constraints set to its parent view). Each cell on the tableView has 4 labels. The top label has a top, leading and trailing constraint to the contentView of the cell and the subsequent labels have a vertical constraint to the label above it. Every label has a heightGreaterThan constraint set and a widthGreaterThan set.

This is how it looks like without the preferredMaxLayoutWidth set (Notice how labels are capped to 1 line). No preferredMaxLayoutWidth

With preferredMaxLayoutWidth. Now the UILabel shows the entire content but has a padding on top and bottom. With preferredMaxLayoutWidth but with top and bottom padding

Edit2: Sample Project: https://www.dropbox.com/s/sdahx9njnsxiv98/MultiLineUILabel.zip?dl=1

10
I have the exact same problem as you. I've even tried hacking the preferredMaxLayoutWidth by adding a few pixels, but this number is highly dependent on how wide the screen is and renders differently on iPhone 6, iPhone 6+ and iPhone 5. Not to mention iPad. Not sure if it is a bug or if there is some other setting / hack to get it to work.Michael Gaylord
I had the same problem as well and I was NOT using auto-layout. The problem only occurred on iPhone 6 and not iPad. No combination of autoresize mask settings made any difference. Eventually I solved the problem by doing a Clean, Restart XCode and Rebuild. Weird, but it resolve the problem where a Clean alone would not. XCode 6.1.1.JasonD
Make sure the label's preferred width is set to Automatic and Explicit isn't checked on the setting panel. This solved the issue for me when using autotlayout.AndyC
Link is dead, please update your answerE-Riddie

10 Answers

8
votes

So what i ended up doing was to create a subclass for the label and override setBounds: and set the preferredMaxLayoutWidth to its bounds.

- (void)setBounds:(CGRect)bounds {
    [super setBounds:bounds];

    if (self.preferredMaxLayoutWidth != bounds.size.width) {
        self.preferredMaxLayoutWidth = bounds.size.width;
        [self setNeedsUpdateConstraints];
}}

Also, in the UITableViewCell subclass, i override the layoutSubiews

- (void)layoutSubviews {
    [super layoutSubviews];

    [self.contentView layoutIfNeeded];

    // labelsCollection is an IBOutletCollection of my UILabel sublasses
    for (UILabel *label in self.labelsCollection) {
        label.preferredMaxLayoutWidth = label.bounds.size.width;
}}

This works all the time. I think the reason for this odd behavior is that the UILabel is initially set to 280px (320px - padding on either side) in the xib file but during run time, it changes its width to accommodate bigger screens (since I set the leading and trailing constraints it increases the width which changes the 'preferredMaxLayoutWidth' to a bigger value). For some reason UILabel doesn't update its preferredMaxLayoutWidth to the bigger value and causes the white space on top and bottom.

That's my hypothesis.

Hope this helps.

2
votes

Anyone looking into this question that already is using maxPreferredLayoutWidth (btw you do not have to subclass, just set this in viewDidLayoutSubviews of your VC) check that you are setting it to the width of the correct view.

Mine was not working because I had:

label1.maxPreferredLayoutWidth = label1.frame.width
label2.maxPreferredLayoutWidth = label1.frame.width

When it should have been:

label1.maxPreferredLayoutWidth = label1.frame.width
label2.maxPreferredLayoutWidth = label2.frame.width

Do you see the difference? I did not for 3 hours lol

1
votes

using this class fixed it for me

import UIKit

class CustomLabel: UILabel {

    override func drawText(in rect: CGRect) {
        if let stringText = text {
            let stringTextAsNSString = stringText as NSString
            let labelStringSize = stringTextAsNSString.boundingRect(with: CGSize(width: self.frame.width, height: CGFloat.greatestFiniteMagnitude), options: NSStringDrawingOptions.usesLineFragmentOrigin, attributes: [NSFontAttributeName: font], context: nil).size
            super.drawText(in: CGRect(x: 0, y: 0, width: self.frame.width, height: ceil(labelStringSize.height)))
        } else {
            super.drawText(in: rect)
        }
    }

}
0
votes

UILabel centers text vertically. I'm guessing what you are seeing is that your constraints are causing the label's view to stretch on the larger device, and the text is, as expected, centering itself vertically on the sized view.

You could try using a non-editable UITextView with UIViewContentModeTop instead.

0
votes

Is there any constraint on the bottom of the label? I believe it's allowing to stretch because of the content hugging priority. Set it to 1000 (required). That will prevent it from expanding beyond its text size. You'll then need to add proper constraints to bottom of label.

0
votes

Because I needed to set preferredMaxLayoutWidth for iOS 7 compatibility, I ended up calling boundingRectWithSize to calculate the required label height and setting an exposed height constraint's constant for the UILabel.

CGRect labelRect = CGRectIntegral([myText boundingRectWithSize:size 
    options:options attributes:attributes context:context]);
self.myLabelHeightConstraint.constant = labelRect.size.height;
0
votes

So this is how I implemented this on swift, In the class extending the UILabel, I override the bounds var with a getter and setter, which sets the super's bounds var and returns it, this way you can add some code to the get and set.

I must say I'm not convinced it's doing all the magic just yet, I still have large open spaces.

    override var bounds:CGRect {
        get {
            return super.bounds
        }
        set {
            super.bounds = newValue
            if (self.preferredMaxLayoutWidth != super.bounds.size.width) {
                self.preferredMaxLayoutWidth = super.bounds.size.width;
                self.setNeedsUpdateConstraints()
            }
        }
    }
0
votes

First of all guys, sorry for my English. I'm novice in English, but I'll try to describe how I fixed this problem.

I faced with this problem in some of my projects. So my solution to fix this is:

  1. In a cell I have the following configuration method: -(void)configureWithSomeObject:parentViewSize: and in tableView:cellForRowAtIndexPath: I created cell like this:

    - (UITableViewCell *)tableView:(UITableView *)tableView  cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    YourCell *cell;
    
    //cell initialization
    
    [cell configureWithSomeObject:nil parentViewSize:tableView.frame.size];
    
    return cell;
    }
    
  2. Inside configuration methods just set preferredMaxLayoutWidth according to parent width:

     - (void)configureWithSomeObject:(NSObject)someObject parentViewSize:(CGSize)parentViewSize {
    // do your stuff
    
    self.someLabel.preferredMaxLayoutWidth = parentViewSize.width;
     }
    

P.S. in case if you have leading/trailing spaces you should subtract it from parentViewSize.width value.

Hope it will be useful for anybody :)

0
votes

I was having a similar issue, and just subclassing the UILabel and overriding setBounds didn't work for me. When stepping through setBounds, I noticed that the height of the label was 39.999999993. So I made the following change, to round it to 40:

- (void)setBounds:(CGRect)bounds
{
    bounds.size.height = ceilf(CGRectGetHeight(bounds));

    [super setBounds: bounds];

    if (self.preferredMaxLayoutWidth != bounds.size.width)
    {
        self.preferredMaxLayoutWidth = bounds.size.width;
        [self setNeedsUpdateConstraints];
    }
}

This fixed it for me, no more extra padding above and below my UILabel on large devices. I also did not have to set the content hugging priority.

0
votes

Had this issue and was losing hours to solve it. @AndyC answer did it for me. I will repeat it here in case someone missed it:

Make sure the label's preferred width is set to Automatic and Explicit isn't checked on the setting panel. This solved the issue for me when using autotlayout

.