UILabel sizeToFit не работает с autolayout ios6

152

Как я должен программно настроить (и каким способом) UILabel, высота которого зависит от его текста? Я пытался настроить его, используя комбинацию раскадровки и кода, но безрезультатно. Каждый рекомендует sizeToFitпри настройке lineBreakModeи numberOfLines. Однако, независимо от того , если я ставлю этот код в viewDidLoad:, viewDidAppear:или viewDidLayoutSubviewsя не могу получить его на работу. Либо я делаю поле слишком маленьким для длинного текста, и оно не увеличивается, либо я делаю его слишком большим и оно не уменьшается.

circuitlego
источник
FWIW тот же код: мне не нужно было использовать label.sizeToFit()в Xcode / viewController, ограничений было достаточно. не создавал лейбл в Playground . До сих пор единственный способ, которым я нашел его для работы на игровой площадке, это делатьlabel.sizeToFit()
Дорогой,

Ответы:

407

Обратите внимание, что в большинстве случаев решение Мэтта работает как положено. Но если это не работает для вас, пожалуйста, читайте дальше.

Чтобы ваша метка автоматически меняла высоту, вам нужно сделать следующее:

  1. Установить ограничения макета для метки
  2. Установить ограничение по высоте с низким приоритетом. Должно быть ниже, чем ContentCompressionResistancePriority
  3. Установите numberOfLines = 0
  4. Установите ContentHuggingPriority выше приоритета высоты надписи
  5. Установите предпочитаемый MaxLayoutWidth для метки. Это значение используется меткой для расчета его высоты

Например:

self.descriptionLabel = [[UILabel alloc] init];
self.descriptionLabel.numberOfLines = 0;
self.descriptionLabel.lineBreakMode = NSLineBreakByWordWrapping;
self.descriptionLabel.preferredMaxLayoutWidth = 200;

[self.descriptionLabel setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical];
[self.descriptionLabel setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical];
[self.descriptionLabel setTranslatesAutoresizingMaskIntoConstraints:NO];
[self addSubview:self.descriptionLabel];

NSArray* constrs = [NSLayoutConstraint constraintsWithVisualFormat:@"|-8-[descriptionLabel_]-8-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(descriptionLabel_)];
[self addConstraints:constrs];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-8-[descriptionLabel_]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(descriptionLabel_)]];
[self.descriptionLabel addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[descriptionLabel_(220@300)]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(descriptionLabel_)]];

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

  1. Установите четыре ограничения. Ограничение по высоте является обязательным. введите описание изображения здесь

  2. Затем перейдите в инспектор атрибутов метки и установите количество строк равным 0. введите описание изображения здесь

  3. Перейдите в инспектор размера метки и увеличьте вертикальное ContentHuggingPriority и вертикальное ContentCompressionResistancePriority.
    введите описание изображения здесь

  4. Выберите и измените ограничение высоты.
    введите описание изображения здесь

  5. И уменьшить приоритет ограничения высоты.
    введите описание изображения здесь

Наслаждаться. :)

Марк Крыжановский
источник
4
ДА! Это именно тот ответ, который я искал. Единственное, что мне нужно было сделать, это установить для contentHuggingPriority и contentCompressionResistancePriority значение required. Я мог бы сделать все это в IB! Огромное спасибо.
Circulego
43
Вы думаете об этом. Либо установите, preferredMaxLayoutWidthлибо закрепите ширину этикетки (или ее правую и левую стороны). Вот и все! Затем он будет автоматически расти и сжиматься вертикально, чтобы соответствовать его содержимому.
Мат
Мне нужно было установить свойство предпочитаемое значение MaxLayoutWidth + закрепить ширину метки. Работает как шарм :)
MartinMoizard,
предпочитаемое значение MaxLayoutWidth и / или закрепление левой и правой сторон не является абсолютным. Приоритеты объятия и сжатия контента всегда будут работать, если их приоритеты выше, чем остальные ограничения.
Эрик Алфорд,
2
^ И я наконец-то выяснил, почему, стоит упомянуть, когда ячейка придерживается в нижней части представления, вам нужно установить приоритет ограничения «снизу» меньше 1000, я поставил его на 750 и все работает отлично. Я предполагаю, что это сократило представление.
Mathijs Segers
65

В iOS 6 при использовании автоматического размещения, если стороны UILabel (или ширина) и верх закреплены, он будет автоматически увеличиваться и уменьшаться по вертикали, чтобы соответствовать его содержимому, без какого-либо кода и без нарушения его сопротивления сжатию или чего-либо еще. Это очень просто.

В более сложных случаях просто установите метки preferredMaxLayoutWidth.

В любом случае, правильная вещь происходит автоматически.

матовый
источник
12
Вы также должны убедиться, что для numberOfLines установлено значение 0, иначе вы не получите перенос.
devongovett
2
Это было недостаточно для меня. Также нужен пункт 2 из ответа @ Mark.
Данял Айтекин
также возможно ли сделать так, чтобы метка росла автоматически на несколько строк без написания кодов?
Noor
Это намного менее сложно, чем принятый ответ. Я сделал два примера ( здесь и здесь ), иллюстрирующих этот ответ.
Сурагч,
@Suragch Используя ограничения, это работает. Но это не работает для того же кода в Playground . На детской площадке вы должны иметьlabel.sizeToFit()
Мед
42

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

Первое, что нужно забыть sizeToFit. Auto Layout будет обрабатывать это от вашего имени на основе внутреннего размера контента.

Поэтому проблема заключается в том, как сделать так, чтобы метка соответствовала ее содержанию с помощью Auto Layout? В частности - потому что вопрос упоминает это - высота. Обратите внимание, что те же принципы применяются к ширине.

Итак, давайте начнем с примера UILabel, высота которого равна 41px:

введите описание изображения здесь

Как вы можете видеть на скриншоте выше, "This is my text"имеет отступы сверху и снизу. Это отступ между высотой UILabel и его содержанием, текстом.

Если мы запустим приложение в симуляторе, то увидим то же самое:

введите описание изображения здесь

Теперь давайте выберем UILabel в Интерфейсном Разработчике и посмотрим на настройки по умолчанию в Инспекторе размера:

введите описание изображения здесь

Обратите внимание на выделенное ограничение выше. Это приоритет при доставке контента . Как Эрика Садун описывает это в превосходной демо- версии Auto Layout iOS , это:

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

Для нас с UILabel основным содержанием является текст.

Вот мы и подошли к сути этого базового сценария. Мы дали нашей текстовой метке два ограничения. Они конфликтуют. Один говорит, что «высота должна быть равна 41 пикселю в высоту» . Другой говорит: «Обними представление о его содержимом, чтобы у нас не было лишних отступов» . В нашем случае обнимите вид текста, чтобы у нас не было лишних отступов.

Теперь, с Auto Layout, с двумя разными инструкциями, которые говорят, делают разные вещи, среда выполнения должна выбирать одну или другую. Это не может сделать оба. UILabel не может иметь высоту 41 пикселя и не иметь отступов.

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

Итак, давайте попробуем. Мое ограничение по высоте имеет приоритет 1000 , что является обязательным . Высота содержания содержимого составляет 250 , что является слабым . Что произойдет, если мы уменьшим приоритет ограничения высоты до 249 ?

введите описание изображения здесь

Теперь мы видим, как волшебство начинает происходить. Давайте попробуем в симе:

введите описание изображения здесь

Потрясающие! Содержание обнимается Только потому, что приоритет 249 высоты меньше, чем приоритет 250 объятия контента . По сути, я говорю: «Высота, которую я здесь указываю, менее важна, чем та, которую я указал для объятия контента» . Итак, контент, обнимающий, побеждает.

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

Оставим делать эквивалент ширины в качестве упражнения для читателя!

Макс Маклеод
источник
3
Большое спасибо за ваше отличное объяснение! Наконец-то я понимаю Приоритет содержания контента.
Kernix
Не могли бы вы также объяснить, что такое приоритет устойчивости к сжатию контента? Спасибо!
Kernix
2
Превосходный ответ Макс! Есть ли какой-то способ ограничить количество линий, работающих таким образом?
Рафаэльюзо,
7

Заметил, что в IOS7 sizeToFit также не работает - возможно, решение может помочь вам

[textView sizeToFit];
[textView layoutIfNeeded];
Ник Вуд
источник
1
@Rambatino Это работает для меня. Добавьте, layoutIfNeededкогда вам нужно setNeedsUpdateConstraints. Но ситуация может быть немного другой.
Гон
Это работало лучше всего для меня, когда я делал поповер. Мне сначала нужно было сделать layoutIfNeeded
Джейсон
4

Еще одна опция для гарантии того, что метка selectedMaxLayoutWidth синхронизируется с шириной метки:

#import "MyLabel.h"

@implementation MyLabel

-(void)setBounds:(CGRect)bounds
{
    [super setBounds:bounds];

    // This appears to be needed for iOS 6 which doesn't seem to keep
    // label preferredMaxLayoutWidth in sync with its width, which 
    // means the label won't grow vertically to encompass its text if 
    // the label's width constraint changes.
    self.preferredMaxLayoutWidth = self.bounds.size.width;
}

@end
Монте Херд
источник
4

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

  • Цель состоит в том, чтобы позволить Auto Layout выполнять свою работу, даже не вызывая sizeToFit (), мы сделаем это, указав правильные ограничения:
  • Укажите верхний, нижний и начальный / конечный пробелы на вашей UILabel
  • Установите для свойства количество строк значение 0
  • Увеличьте приоритет содержания контента до 1000
  • Уменьшите приоритет сопротивления сжатию содержимого до 500
  • На нижнем ограничении контейнера уменьшите приоритет до 500

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

ArturT
источник
3

В моем случае я создавал подкласс UIView, который содержал UILabel (неизвестной длины). В iOS7 код был прост: устанавливайте ограничения, не беспокойтесь об объятии контента или сопротивлении сжатию, и все работало, как ожидалось.

Но в iOS6 UILabel всегда была обрезана до одной строки. Ни один из ответов выше не работал для меня. Настройки содержания и сопротивления сжатию игнорировались. Единственное решение, которое предотвратило обрезку, заключалось в том, чтобы на метке было указано предпочтительное значение MaxLayoutWidth. Но я не знал, как установить предпочтительную ширину, так как размер его родительского представления был неизвестен (действительно, это будет определяться содержимым).

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

- (void)layoutSubviews
{
    // Autolayout hack required for iOS6
    [super layoutSubviews];
    self.bodyLabel.preferredMaxLayoutWidth = self.bodyLabel.frame.size.width;
    [super layoutSubviews];
}
sumizome
источник
Это был именно тот сценарий, который у меня был ... иногда приятно знать, что ты не один.
daveMac
Адам, я борюсь с тем, что ты сказал, отлично работало в ios7. У меня есть пользовательская ячейка с uiview и uilabel. Я могу сделать так, чтобы метка соответствовала содержанию, но представление не помогает, независимо от того, какие ограничения я на него накладываю. Как ты получил это на работу? Клянусь, я попробовал все, но это не берет высоту этикетки
denikov
3

Я добавил UILabelпрограммно, и в моем случае этого было достаточно:

label.translatesAutoresizingMaskIntoConstraints = false
label.setContentCompressionResistancePriority(UILayoutPriorityRequired, forAxis: .Vertical)
label.numberOfLines = 0
Mixel
источник
2

Я решил с помощью xCode6 поставить «Предпочитаемую ширину» в режим «Автоматически» и закрепить верхнюю, верхнюю и конечную метки введите описание изображения здесь

Kazzar
источник
0
UIFont *customFont = myLabel.font;
CGSize size = [trackerStr sizeWithFont:customFont
                             constrainedToSize:myLabel.frame.size // the size here should be the maximum size you want give to the label
                                 lineBreakMode:UILineBreakModeWordWrap];
float numberOfLines = size.height / customFont.lineHeight;
myLabel.numberOfLines = numberOfLines;
myLabel.frame = CGRectMake(258, 18, 224, (numberOfLines * customFont.lineHeight));
Свати
источник
0

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

Во всяком случае, для меня сработало добавление требуемого ограничения высоты для UILabel и установка его вручную на правильную высоту при вызове intrinsicContentSize. Если у вас нет UILabel, содержащегося в другом UIView, вы можете попробовать создать подкласс UILabel и предоставить аналогичную реализацию, сначала установив ограничение высоты, а затем вернув
[super instrinsicContentSize]; вместо [self.containerview intrinsiceContentSize]; как я делаю ниже, что специфично для моего подкласса UIView.

- (CGSize)intrinsicContentSize
{
    CGRect expectedTextBounds = [self.titleLabel textRectForBounds:self.titleLabel.bounds limitedToNumberOfLines:1];
    self.titleLabelHeightConstraint.constant = expectedTextBounds.size.height;
    return [self.containerView intrinsicContentSize];

}

Отлично работает сейчас на iOS 7 и iOS 8.

n8tr
источник
Стоит отметить, что если вы правильно настроили свое представление (т.е. ваши ограничения верны, и вы все настроили на правильных этапах жизненного цикла представления), вам никогда не придется переопределять intrinsicContentSize. Среда выполнения должна быть в состоянии рассчитать это на основе ваших правильно установленных ограничений.
Джон Роджерс
Теоретически это должно быть правильно, но у нас нет доступа ко всем частным API и реализациям Apple, и они не являются безошибочными и имеют ошибки в своем коде.
n8tr
... но [super intrinsicContentSize] должен позаботиться об этом, вот что я говорю. Если вы правильно реализовали свой автоматический макет.
Джон Роджерс
0

Решение, которое сработало для меня; Если ваша UILabel имеет фиксированную ширину, измените ограничение с constant =на constant <=в вашем файле интерфейса

введите описание изображения здесь

Алекс Браун
источник
-1

В моем случае при использовании меток в UITableViewCell метка at изменится, но высота превысит высоту ячейки таблицы. Это то, что сработало для меня. Я сделал в соответствии с Максом Маклаудом, а затем убедился, что высота ячейки была установлена ​​на UITableViewAutomaticDimension.

Вы можете добавить это в вашем init или awakeFromNib,

self.tableView.rowHeight = UITableViewAutomaticDimension.  

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

В 8.0 есть проблема, которая также требует, чтобы это было установлено в коде.

Мария
источник