Reputation: 1365
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:
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).
With preferredMaxLayoutWidth. Now the UILabel shows the entire content but has a padding on top and bottom.
Edit2: Sample Project: https://www.dropbox.com/s/sdahx9njnsxiv98/MultiLineUILabel.zip?dl=1
Upvotes: 25
Views: 11638
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
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
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
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
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:
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;
}
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
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
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
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
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
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