newDeveloper
newDeveloper

Reputation: 1365

Autolayout adding extra padding on top and bottom of UILabel iPhone 6/6+ when preferredMaxLayoutWidth is set

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

Upvotes: 25

Views: 11638

Answers (10)

Mile Dev
Mile Dev

Reputation: 670

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

.

Upvotes: 0

user1974368
user1974368

Reputation: 472

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)
        }
    }

}

Upvotes: 1

Abbey Jackson
Abbey Jackson

Reputation: 885

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

Upvotes: 2

koen
koen

Reputation: 5729

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.

Upvotes: 0

Oleg  Sorochich
Oleg Sorochich

Reputation: 1

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 :)

Upvotes: 0

Mathijs Segers
Mathijs Segers

Reputation: 6238

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()
            }
        }
    }

Upvotes: 0

newDeveloper
newDeveloper

Reputation: 1365

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.

Upvotes: 8

AlexD
AlexD

Reputation: 833

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;

Upvotes: 0

Henry T Kirk
Henry T Kirk

Reputation: 961

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.

Upvotes: 0

stromdotcom
stromdotcom

Reputation: 381

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.

Upvotes: 0

Related Questions