AutoLayout связывает два UILabels с одинаковым размером шрифта

У меня есть два UILabels рядом друг с другом в строке с левыми и правыми настройками, чтобы он выглядел ниже.

 |-Some text left adjusted----------some other text right adjusted-|

Обе метки имеют параметры FontSizeToFitWidth = YES и связаны друг с другом со следующим ограничением

[NSLayoutConstraint constraintWithItem:_rightLabel
                    attribute:NSLayoutAttributeLeft
                    relatedBy:NSLayoutRelationGreaterThanOrEqual
                    toItem:_leftLabel
                    attribute:NSLayoutAttributeRight
                    multiplier:1
                    constant:10]

Чтобы они занимали столько места, сколько могут, и если места для исходного размера шрифта недостаточно, он будет уменьшен благодаря настройкеFontSizeToFitWidth, чтобы текст не был усечен.

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

Ответы

Ответ 1

Из Документация UILabel на adjustsFontSizeToWidth:

Обычно текст метки нарисован шрифтом, указанным в свойстве шрифта. Однако, если это свойство установлено в YES, а текст в свойстве text превышает ограничивающий прямоугольник меток, приемник начинает уменьшать размер шрифта до тех пор, пока строка не будет соответствовать или не будет достигнут минимальный размер шрифта.

Я выхожу из этого, что обновленный шрифт вычисляется во время рисования, а свойство font считывается, а не записывается в. Поэтому я считаю, что предложение Эндрю использовать KVO в свойстве font не будет работать.

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

Как отмечает Джексон в комментариях, этот очень удобный метод NSString, чтобы получить фактический шрифт, устарел в iOS 7. Технически вы все равно можете его использовать до тех пор, пока он удалены.

Другой альтернативой является циклическое масштабирование шрифтов до тех пор, пока вы не найдете тот, который будет соответствовать обеим меткам. Я смог заставить его работать нормально; здесь пример проекта, который показывает, как я это сделал.

Кроме того, здесь код в случае, если ссылка перестает работать:

- (void)viewDidLoad
{
    [super viewDidLoad];

    NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:_rightLabel
                                                                  attribute:NSLayoutAttributeLeft
                                                                  relatedBy:NSLayoutRelationGreaterThanOrEqual
                                                                     toItem:_leftLabel
                                                                  attribute:NSLayoutAttributeRight
                                                                 multiplier:1
                                                                   constant:10];

    [self.view addConstraint:constraint];
}

- (IBAction)makeRightLabelLongerPressed:(id)sender {
    self.rightLabel.text = @"some much longer right label text";
}

- (IBAction)adjustLabelSizes:(id)sender {
    NSLog(@"Attempting to adjust label sizes…");

    CGFloat minimumScaleFactor = fmaxf(self.rightLabel.minimumScaleFactor, self.leftLabel.minimumScaleFactor);;
    UIFont * startingFont = self.rightLabel.font;

    for (double currentScaleFactor = 1.0; currentScaleFactor > minimumScaleFactor; currentScaleFactor -= 0.05) {
        UIFont *font = [startingFont fontWithSize:startingFont.pointSize * currentScaleFactor];
        NSLog(@"  Attempting font with scale %f (size = %f)…", currentScaleFactor, font.pointSize);

        BOOL leftLabelWorks = [self wouldThisFont:font workForThisLabel:self.leftLabel];
        BOOL rightLabelWorks = [self wouldThisFont:font workForThisLabel:self.rightLabel];
        if (leftLabelWorks && rightLabelWorks) {
            NSLog(@"    It fits!");
            self.leftLabel.font = font;
            self.rightLabel.font = font;
            return;
        } else {
            NSLog(@"    It didn't fit. :-(");
        }

    }

    NSLog(@"  It won't fit without violating the minimum scale (%f), so set both to minimum.  Some text may get truncated.", minimumScaleFactor);

    UIFont *minimumFont = [self.rightLabel.font fontWithSize:self.rightLabel.font.pointSize * self.rightLabel.minimumScaleFactor];
    self.rightLabel.font = minimumFont;
    self.leftLabel.font = minimumFont;
}

- (BOOL) wouldThisFont:(UIFont *)testFont workForThisLabel:(UILabel *)testLabel {
    NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys:testFont, NSFontAttributeName, nil];
    NSAttributedString *as = [[NSAttributedString alloc] initWithString:testLabel.text attributes:attributes];
    CGRect bounds = [as boundingRectWithSize:CGSizeMake(CGRectGetWidth(testLabel.frame), CGFLOAT_MAX) options:(NSStringDrawingUsesLineFragmentOrigin) context:nil];
    BOOL itWorks = [self doesThisSize:bounds.size fitInThisSize:testLabel.bounds.size];
    return itWorks;
}

- (BOOL)doesThisSize:(CGSize)aa fitInThisSize:(CGSize)bb {
    if ( aa.width > bb.width ) return NO;
    if ( aa.height > bb.height ) return NO;
    return YES;
}

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

Ответ 2

Я нашел лучшее решение. Просто добавьте в ur текстовые пространства от начала до конца до uilabel, у которых меньше длины текста. И шрифт будет таким же.

Ответ 3

Swift 5

extension UILabel {
    var actualFontSize: CGFloat {
        guard let attributedText = attributedText else { return font.pointSize }
        let text = NSMutableAttributedString(attributedString: attributedText)
        text.setAttributes([.font: font as Any], range: NSRange(location: 0, length: text.length))
        let context = NSStringDrawingContext()
        context.minimumScaleFactor = minimumScaleFactor
        text.boundingRect(with: frame.size, options: .usesLineFragmentOrigin, context: context)
        let adjustedFontSize: CGFloat = font.pointSize * context.actualScaleFactor
        return adjustedFontSize
    } 
}

Использование:

firstLabel.text = firstText
secondLabel.text = secondText
view.setNeedsLayout()
view.layoutIfNeeded()
let smallestSize = min(firstLabel.actualFontSize, secondLabel.actualFontSize)
firstLabel.font = firstLabel.font.withSize(smallestSize)
secondLabel.font = secondLabel.font.withSize(smallestSize)

Ответ 4

Я решил эту проблему следующим образом:

Swift 5

extension UILabel {
    var actualFontSize: CGFloat {
        guard let attributedText = attributedText else { return font.pointSize }
        let text = NSMutableAttributedString(attributedString: attributedText)
        text.setAttributes([.font: font as Any], range: NSRange(location: 0, length: text.length))
        let context = NSStringDrawingContext()
        context.minimumScaleFactor = minimumScaleFactor
        text.boundingRect(with: frame.size, options: .usesLineFragmentOrigin, context: context)
        let adjustedFontSize: CGFloat = font.pointSize * context.actualScaleFactor
        return adjustedFontSize
    } 
}

Использование:

firstLabel.text = firstText
secondLabel.text = secondText
view.setNeedsLayout()
view.layoutIfNeeded()
let smallestSize = min(firstLabel.actualFontSize, secondLabel.actualFontSize)
firstLabel.font = firstLabel.font.withSize(smallestSize)
secondLabel.font = secondLabel.font.withSize(smallestSize)

Ответ 5

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

В вашем методе -viewDidLoad:

// Add self as an observer of _rightLabel font property
[_rightLabel addObserver:self forKeyPath:@"font" options:NSKeyValueObservingOptionNew context:NULL];

В той же реализации контроллера (self в приведенном выше контексте фрагмента кода):

// Observe changes to the font property of _rightLabel
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    if (object == _rightLabel && [keyPath isEqualToString:@"font"]) {
        // Set _leftLabel font property to be the new value set on _rightLabel
        _leftLabel.font = change[NSKeyValueChangeNewKey];
    }
}