UILabel + AutoLayout =基线对齐错误

eme*_*gro 18 nsattributedstring ios autolayout

对齐两个标签时遇到问题.两个例子来说明问题

示例1(确定)

[leftLabel setText:@"03"];
[rightLabel setText:@"Description3"];
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述

例2(NOK)

[leftLabel setText:@"03"];
[rightLabel setAttributedText:[[NSAttributedString alloc] initWithString:@"Description3"]];
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述

在这两个示例中,布局约束都是这样

[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-bigMargin-[leftLabel]-bigMargin-[rightLabel]-bigMargin-|"
                                        options:NSLayoutFormatAlignAllBaseline
                                        metrics:metrics
                                          views:views];
Run Code Online (Sandbox Code Playgroud)

问题是正确的标签,当文本是属性标签时,它被绘制在下面一点,如图中所示,并且对齐结果错误.

为什么?我能用这UIlabel两种方法解决这个问题吗?

编辑:

在GitHub上创建了一个项目,并对此进行了测试.这里的问题是即使没有NSAttributdString我也有问题!查看带有数字的标签,与说明和金额没有正确对齐.

在此输入图像描述

我在这里粘贴单元格的代码,但必须在项目中看到整个场景.

- (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;
}
Run Code Online (Sandbox Code Playgroud)

Lom*_*baX 12

好的,最后一个例子问题更清楚了.您必须知道的第一件事:默认情况下,UILabel内的文本垂直对齐在标签的中心.
AFAIK您无法更改垂直对齐以使文本与基线对齐.

现在,看看附图.

对照

在第一个示例中,我留下了示例项目的所有默认值.您可以看到日期标签和描述标签完全对齐:Autolayout对齐两个标签的边界(仅仅是视图,其中包含其他私有子视图).
但是,字体大小不同.对于日期标签是默认系统大小(17),对于您指定的描述20.
现在,如果两个标签对齐,则文本在标签中垂直居中,并且字体大小不同,显然是基线两个文本不会对齐.

在第二个示例中,我使用相同的字体大小,您可以看到对齐是正确的.

那么,可能的解决方案有两个:

  • 在所有标签中使用相同的字体(具有相同的大小)
  • 如果要使用不同种类/大小的字体,则必须更改标签的位置/大小以使基线正确对齐

最后一点可以通过一些计算来完成,你可以在这里找到一些例子:链接

编辑

好的,我发布了一个例子.第一件事:似乎在发布的链接上有一个错误.ascender + descender + 1等于lineHeight,而不是pointSize.我让作者纠正它.

因此,您可以继承UILabel,覆盖viewForBaselineLayout并执行类似的操作.您必须添加一个baselineView实例变量并将其添加为子视图UILabel,因为AutoLayout希望视图对齐为标签的子视图.

// 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;

}
Run Code Online (Sandbox Code Playgroud)

通过这种方法,您必须处理以下几点:

  • 标签必须在自动布局调用时具有大小viewForBaselineLayout,并且其高度在以后不得更改.因此,在布局过程发生之前,您必须使标签符合其内容大小.
  • 您已根据上述建议调整约束(现在您正在修复,例如,dayLabel与monthLabel,但您不能,因为dayLabel现在必须"浮动").

我附上你的项目的一些更新和颜色用于调试,只有dayLabel和descriptionLabel(我删除了其他)和更新的约束.

布局错误更新