I'm having problem while aligning two labels. Two examples to illustrate the problem
[leftLabel setText:@"03"];
[rightLabel setText:@"Description3"];
[leftLabel setText:@"03"];
[rightLabel setAttributedText:[[NSAttributedString alloc] initWithString:@"Description3"]];
In both examples the layout constraint is this
[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-bigMargin-[leftLabel]-bigMargin-[rightLabel]-bigMargin-|"
options:NSLayoutFormatAlignAllBaseline
metrics:metrics
views:views];
The problem is the right label, when the text is an attributed one it is drawn one point below, as seen in the images, and the alignment results wrong.
Why? Can I solve this using UIlabel
and both approaches?
EDIT:
I've created a project in GitHub with a test on this. The question here is I'm having the issue even without NSAttributdString! Look at the label with the number, is not correctly aligned with the description and amount.
I paste here the code of the cell but the whole scenario must be seen in the project.
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
UIView *contentView = [self contentView];
[contentView setBackgroundColor:[UIColor clearColor]];
dayLabel_ = [[UILabel alloc] initWithFrame:CGRectZero];
[dayLabel_ setTranslatesAutoresizingMaskIntoConstraints:NO];
[contentView addSubview:dayLabel_];
monthLabel_ = [[UILabel alloc] initWithFrame:CGRectZero];
[monthLabel_ setTranslatesAutoresizingMaskIntoConstraints:NO];
[monthLabel_ setFont:[UIFont boldSystemFontOfSize:13.0f]];
[contentView addSubview:monthLabel_];
descriptionLabel_ = [[UILabel alloc] initWithFrame:CGRectZero];
[descriptionLabel_ setTranslatesAutoresizingMaskIntoConstraints:NO];
[descriptionLabel_ setFont:[UIFont systemFontOfSize:20.0f]];
[contentView addSubview:descriptionLabel_];
conceptLabel_ = [[UILabel alloc] initWithFrame:CGRectZero];
[conceptLabel_ setTranslatesAutoresizingMaskIntoConstraints:NO];
[conceptLabel_ setLineBreakMode:NSLineBreakByTruncatingTail];
[conceptLabel_ setFont:[UIFont systemFontOfSize:12.0f]];
[contentView addSubview:conceptLabel_];
amountLabel_ = [[UILabel alloc] initWithFrame:CGRectZero];
[amountLabel_ setTranslatesAutoresizingMaskIntoConstraints:NO];
[contentView addSubview:amountLabel_];
// Constraints
NSDictionary *views = NSDictionaryOfVariableBindings(contentView, dayLabel_, monthLabel_, descriptionLabel_, conceptLabel_, amountLabel_);
NSDictionary *metrics = @{ @"bigMargin" : @12 };
[descriptionLabel_ setContentCompressionResistancePriority:UILayoutPriorityDefaultLow forAxis:UILayoutConstraintAxisHorizontal];
[conceptLabel_ setContentCompressionResistancePriority:UILayoutPriorityDefaultLow forAxis:UILayoutConstraintAxisHorizontal];
[contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-bigMargin-[dayLabel_][monthLabel_]"
options:NSLayoutFormatAlignAllLeading
metrics:metrics
views:views]];
[contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-bigMargin-[descriptionLabel_][conceptLabel_]"
options:NSLayoutFormatAlignAllLeading
metrics:metrics
views:views]];
[contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-bigMargin-[dayLabel_]-bigMargin-[descriptionLabel_]-(>=bigMargin)-[amountLabel_]-bigMargin-|"
options:NSLayoutFormatAlignAllBaseline
metrics:metrics
views:views]];
[contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-bigMargin-[monthLabel_]-bigMargin-[conceptLabel_]-bigMargin-|"
options:NSLayoutFormatAlignAllBaseline
metrics:metrics
views:views]];
}
return self;
}
Ok, with the last example the problem is more clear.
The first thing you must know: the text inside an UILabel is, by default, vertically aligned at the center of the label.
AFAIK you can't change the vertical align to have the text aligned to the base line.
Now, look at the attached image.
In the first example, I leaved all the default values of your sample project.
You can see that the day label and the description label are perfectly aligned: Autolayout aligns the bounds of the two labels (that are simply views, with other private subviews inside).
But, the font size is different. For the day label is the default system size (17), for the description you specified 20.
Now, if the two labels are aligned, the text is vertically center in the label, and the font size is different, obviously the baseline of the two text won't be aligned.
In the second example, I used the same font size, and you can see that the alignment is correct.
So, the possible solutions are two:
This last point can be done with some calculation, and you can find some example here: Link
EDIT
Ok, I post an example.
First thing: it seems that, at the link posted, there is an error. ascender + descender + 1
is equal to the lineHeight
, and not to the pointSize
. I asked the author to correct it.
So, you can subclass UILabel, override viewForBaselineLayout and do something similar.
You have to add a baselineView
instance variable and add it as a subview of the UILabel
, since AutoLayout want the view to align to be a subview of the label.
// please note: you may need some error checking
- (UIView *)viewForBaselineLayout
{
// create the view if not exists, start with rect zero
if (!self.baselineView)
{
self.baselineView = [[UIView alloc] initWithFrame:CGRectZero];
self.baselineView.backgroundColor = [UIColor clearColor];
[self addSubview:self.baselineView];
}
// this is the total height of the label
float viewHeight = self.bounds.size.height;
// calculate the space that is above the text
float spaceAboveText = (viewHeight - self.font.lineHeight) / 2;
// this is the height of the view we want to align to
float baselineViewHeight = spaceAboveText + self.font.ascender + 1;
// if you have 26.6545 (for example), the view takes 26.0 for the height. This is not good, so we have to round the number correctly (to the upper value if >.5 and to the lower if <.5)
int integerBaseline = (int)(baselineViewHeight + 0.5f);
// update the frame of the view
self.baselineView.frame = CGRectMake(0, 0, self.bounds.size.width, (float)integerBaseline);
return self.baselineView;
}
With this approach you have to take care of few things:
viewForBaselineLayout
, and its height must not change after. So you have to fit the label to it's content size before the layout process takes place.I attach your project with some updates and colors for debug, only with dayLabel and descriptionLabel (I removed the others) and with updated contraints.
Wrong Layout Updated
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With