2013-03-19 2 views
15

Итак, у меня есть настройка моего представления в IB, чтобы эта текстовая метка совпадала с верхней частью миниатюры с помощью ограничений.Вертикально выровнять текст UILabel с ограничениями и без обертки (автоматическая компоновка, одна строка)

enter image description here

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

enter image description here

existing solution включает либо вызова sizeToFit или обновления кадра UILabel, чтобы соответствовать высоте текста. К сожалению, последнее (хотя и уродливое) решение плохо работает с ограничениями, когда вы не должны обновлять фрейм. Первое решение в основном не работает, когда вам нужно иметь автозапуск текста, пока он не обрезается. (Таким образом, он не работает с ограниченным числом строк и автозагрузкой).

Теперь о том, почему внутренний размер (высота) uilabel не обновляется, как это делает ширина, когда он установлен в натуральном размере с помощью «Размер, соответствующий содержанию», находится вне меня. Похоже, это определенно должно, но это не так.

Так что мне осталось искать альтернативные решения. Насколько я вижу, вам может потребоваться установить ограничение высоты на метке и отрегулировать константу высоты после вычисления высоты текста. У кого-то есть хорошее решение?

+0

@ hans-sjunnesson: вы пробовали автоматическую усадку с базовой настройкой 'None'? –

+0

@ JörnEyrich, в то время как текст будет прикреплен к верхней части рамки, фактический кадр UILabel не сжимается вверх. Это означает, что если у вас есть, скажем, суб-метка под ним, суб-метка не будет следовать этикетке вверх, поскольку она сжимается. –

+0

Проверьте http://stackoverflow.com/a/4942766/1039901 –

ответ

7

Эта проблема - реальная проблема PITA. Это не помогает устаревать API в iOS7 или что API-интерфейс замены iOS7 не работает. Мля!

Ваше решение хорошо, однако оно использует устаревший API (sizeWithFont:minFontSize:actualFontSize:forWidth:lineBreakMode:), и это не очень хорошо инкапсулировано - вам нужно скопировать этот код в любые ячейки или представления, где вы хотите этого поведения. С положительной стороны это довольно эффективно! Одна ошибка может заключаться в том, что ярлык еще не был выложен, когда вы делаете расчет, но вы выполняете расчет на основе его ширины.

Я предлагаю вам инкапсулировать это поведение в подклассе UILabel.Помещая вычисление размера в переопределенном методе intrinsicContentSize, метка будет автоматически настраиваться. Я написал следующее, который включает в себя код, который будет выполняться на iOS6, и моя версия с использованием не устаревших API для iOS7 или лучше:

@implementation TSAutoHeightLabel 

- (CGSize) intrinsicContentSize 
{ 
    NSAssert(self.baselineAdjustment == UIBaselineAdjustmentAlignCenters, @"Please ensure you are using UIBaselineAdjustmentAlignCenters!"); 

    NSAssert(self.numberOfLines == 1, @"This is only for single-line labels!"); 

    CGSize intrinsicContentSize; 

    if ([self.text respondsToSelector: @selector(boundingRectWithSize:options:attributes:context:)]) 
    { 
     NSStringDrawingContext* context = [NSStringDrawingContext new]; 
     context.minimumScaleFactor = self.minimumScaleFactor; 

     CGSize inaccurateSize = [self.text boundingRectWithSize: CGSizeMake(self.bounds.size.width, CGFLOAT_MAX) 
                 options: NSStringDrawingUsesLineFragmentOrigin 
                attributes: @{ NSFontAttributeName : self.font } 
                 context: context].size; 

     CGSize accurateSize = [self.text sizeWithAttributes: @{ NSFontAttributeName : [UIFont fontWithName: self.font.fontName size: 12.0] } ]; 

     CGFloat accurateHeight = accurateSize.height * inaccurateSize.width/accurateSize.width; 

     intrinsicContentSize = CGSizeMake(inaccurateSize.width, accurateHeight); 
    } 
    else 
    { 
     CGFloat actualFontSize; 

#pragma GCC diagnostic push 
#pragma GCC diagnostic ignored "-Wdeprecated-declarations" 

     [self.text sizeWithFont: self.font 
        minFontSize: self.minimumFontSize 
       actualFontSize: &actualFontSize 
         forWidth: self.frame.size.width 
        lineBreakMode: NSLineBreakByTruncatingTail]; 

#pragma GCC diagnostic pop 

     CGRect lineBox = CTFontGetBoundingBox((__bridge CTFontRef)([UIFont fontWithName: self.font.fontName size: actualFontSize])); 

     intrinsicContentSize = lineBox.size; 
    } 

    return intrinsicContentSize; 
} 

@end 

Эта реализация не является совершенным. Я должен был обеспечить использование baselineAdjustment == UIBaselineAdjustmentAlignCenters, и я не уверен на 100%, я понимаю, почему. И я не доволен обручами, которые мне пришлось перепрыгнуть, чтобы получить точную высоту текста. Существует также разница в пикселях между тем, что производит мой расчет, и вашим. Не стесняйтесь играть с ним и при необходимости настраивайте :)

API boundingRectWithSize:options:attributes:context кажется мне сломанным. Хотя он (в основном!) Правильно сжимает текст до размера ввода, он не вычисляет правильную высоту! Высота, которую он возвращает, основана на высоте строки поставляемого шрифта, даже если масштабирование находится в игре. Мое предположение - вот почему UILabel не имеет этого поведения по умолчанию? Моим обходным путем является вычисление неограниченного размера, где высота и ширина являются точными, а затем используйте соотношение между ограниченной и неограниченной шириной для вычисления точной высоты для ограниченного размера. Что такое ПИТА. На форумах Apple dev есть много жалоб и здесь, на SO, которые указывают, что этот API имеет ряд проблем, подобных этому.

+0

Я чувствую себя как марсианский, чтобы спросить, а что такое PITA? – Kuzgun

+1

Боль в a ... Подождите, у марсиан есть ослы? –

+0

Вы получаете щедрость Том. Несмотря на то, что это может показаться взломом, реализация связана с вычислением внутреннего размера метки. –

-3

Что вы ищете, это две строки кода.

myLabel.numberOfLines = 0; 
myLabel.lineBreakMode = UILineBreakModeWordWrap; 

и вы также найдете это в Инспекторе Атрибутов под «Line Breaks» и «Линии».

+0

Нет, если вы не хотите, чтобы он был обернут. –

2

Так что я нашел обходное решение. Это немного рискованно, но он работает.

Так что я сделал, добавлял ограничение по высоте к моей строке текста в IB и хватало ссылку на это на мой взгляд.

Тогда в layoutSubviews, я обновить ограничение высоты в зависимости от размера шрифта, который я должен вычислить:

- (void)layoutSubviews { 
    if (self.titleLabel.text) { 
     CGFloat actualFontSize; 
     CGSize titleSize = [self.titleLabel.text sizeWithFont:self.titleLabel.font minFontSize:9.0 actualFontSize:&actualFontSize forWidth:self.titleLabel.frame.size.width lineBreakMode:NSLineBreakByTruncatingTail]; 
     CGRect lineBox = CTFontGetBoundingBox((__bridge CTFontRef)([UIFont fontWithName:@"ProximaNova-Regular" size:actualFontSize])); 
     self.titleHeightConstraint.constant = lineBox.size.height; 
    } 
    [super layoutSubviews]; 
} 

На первом я просто установив его фактического размера шрифта, но даже с (* 1.2) он все еще обрезал меньшие размеры шрифта. Ключ использовал CTFontGetBoundingBox с размером шрифта, определенным по моим расчетам.

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

1

TomSwift благодарит за ваш ответ, я действительно боролся с этой проблемой.

Если кто-то до сих пор получаю странное поведение, я должен был изменить:

intrinsicContentSize = CGSizeMake (inaccurateSize.width, accurateHeight);

к

intrinsicContentSize = CGSizeMake (inaccurateSize.width, accurateHeight * 2);

затем он работал как шарм.