UILabel + AutoLayout = ошибка в выравнивании базовой линии - программирование
Подтвердить что ты не робот

UILabel + AutoLayout = ошибка в выравнивании базовой линии

У меня возникают проблемы при выравнивании двух меток. Два примера для иллюстрации проблемы

Пример 1 (ОК)

[leftLabel setText:@"03"];
[rightLabel setText:@"Description3"];

enter image description here

Пример 2 (NOK)

[leftLabel setText:@"03"];
[rightLabel setAttributedText:[[NSAttributedString alloc] initWithString:@"Description3"]];

enter image description here

В обоих примерах ограничение компоновки - это

[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-bigMargin-[leftLabel]-bigMargin-[rightLabel]-bigMargin-|"
                                        options:NSLayoutFormatAlignAllBaseline
                                        metrics:metrics
                                          views:views];

Проблема - это правильная метка, когда текст отнесен к ней, она нарисована на одну точку ниже, как видно на изображениях, и результаты выравнивания ошибочны.

Почему? Могу ли я решить эту проблему, используя UIlabel и оба подхода?

EDIT:

Я создал проект в GitHub с тестом на это. Вопрос здесь в том, что у меня проблема, даже без NSAttributdString! Посмотрите на метку с номером, неправильно выровненное с описанием и количеством.

enter image description here

Я вставляю здесь код ячейки, но весь сценарий должен быть замечен в проекте.

- (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;
}
4b9b3361

Ответ 1

Хорошо, с последним примером проблема более ясна. Первое, что вы должны знать: текст внутри UILabel по умолчанию вертикально выровнен по центру метки.
AFAIK вы не можете изменить вертикальное выравнивание, чтобы текст был выровнен по базовой линии.

Теперь посмотрим на прикрепленное изображение.

Comparison

В первом примере я выделил все значения по умолчанию для вашего образца проекта. Вы можете видеть, что метка дня и метка описания полностью выровнены: 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;

}

При таком подходе вам нужно позаботиться о нескольких вещах:

  • метка должна иметь размер во время автовызов viewForBaselineLayout, и ее высота не должна изменяться после. Таким образом, вы должны подгонять этикетку до размера содержимого до того, как будет выполнен процесс компоновки.
  • вы скорректируете свои ограничения в соответствии с приведенным выше советом (теперь вы фиксируете, например, dayLabel с помощью метки месяца, но вы не можете, так как dayLabel должен "плавать" сейчас).

Я прикладываю ваш проект с некоторыми обновлениями и цветами для отладки, только с dayLabel и descriptionLabel (я удалял остальные) и с обновленными ограничениями.

Неверный макет обновлен