AutoLayout со скрытыми UIViews?
Мне кажется, что это довольно распространенная парадигма, чтобы показать/скрыть UIViews
, чаще всего UILabels
, в зависимости от бизнес-логики. Мой вопрос: как лучше всего использовать AutoLayout для ответа на скрытые представления, как если бы их кадр был 0x0. Ниже приведен пример динамического списка из 1-3 функций.
![Dynamic features list]()
Сейчас у меня есть 10px верхнее пространство от кнопки до последней метки, которая, очевидно, не будет скользить, когда ярлык будет скрыт. На данный момент я создал выход для этого ограничения и изменял константу в зависимости от того, сколько ярлыков я показываю. Это, очевидно, немного хаки, так как я использую отрицательные постоянные значения, чтобы нажимать кнопку над скрытыми фреймами. Это также плохо, потому что это не ограничивается действительными элементами макета, просто подлые статические вычисления, основанные на известных высотах/заполнении других элементов, и, очевидно, борьба с тем, для чего был создан AutoLayout.
Я мог бы просто создать новые ограничения в зависимости от динамических ярлыков, но это много микроменеджмента и много многословия для попытки просто свернуть некоторые пробелы. Есть ли лучшие подходы? Изменение размера кадра 0,0 и разрешение AutoLayout делать свою работу без каких-либо ограничений? Полностью удалить взгляды?
Честно говоря, просто изменение константы из контекста скрытого представления требует отдельной строки кода с простым вычислением. Повторное создание новых ограничений с помощью constraintWithItem:attribute:relatedBy:toItem:attribute:multiplier:constant:
кажется настолько тяжелым.
Ответы
Ответ 1
Мое личное предпочтение показывать/скрывать представления - это создать IBOutlet с соответствующим ограничением ширины или высоты.
Затем я обновляю значение constant
до 0
, чтобы скрыть, или независимо от значения, которое должно отображаться.
Большое преимущество этого метода заключается в том, что будут сохраняться относительные ограничения. Например, скажем, у вас есть вид A и вид B с горизонтальным разрывом x. Когда в представлении Ширина ширины constant
установлена на 0.f
, тогда просмотр B будет перемещаться влево, чтобы заполнить это пространство.
Нет необходимости добавлять или удалять ограничения, что является тяжеловесной операцией. Просто обновление ограничения constant
сделает трюк.
Ответ 2
Решение использовать константу 0
при скрытии и другую константу, если вы показываете ее снова, является функциональным, но неудовлетворительно, если ваш контент имеет гибкий размер. Вам нужно будет измерить ваш гибкий контент и установить постоянную обратную сторону. Это кажется неправильным и имеет проблемы, если контент изменяет размер из-за событий сервера или интерфейса.
У меня есть лучшее решение.
Идея состоит в том, чтобы установить правило высоты 0, чтобы иметь высокий приоритет, когда мы скрываем элемент, чтобы он не занимал места автозапуска.
Вот как вы это делаете:
1. установите ширину (или высоту) 0 в построителе интерфейса с низким приоритетом.
![setting width 0 rule]()
Интерфейс Builder не будет кричать о конфликтах, потому что приоритет низкий. Проверьте поведение по высоте, временно установив приоритет на 999 (1000 запрещается мутировать программно, поэтому мы его не будем использовать). Конструктор интерфейсов, вероятно, теперь будет кричать о конфликтующих ограничениях. Вы можете исправить это, установив приоритеты для связанных объектов на 900 или около того.
2. Добавьте выход, чтобы вы могли изменить приоритет ограничения ширины в коде:
![outlet connection]()
3. Отрегулируйте приоритет, когда вы спрячете свой элемент:
cell.alertTimingView.hidden = place.closingSoon != true
cell.alertTimingWidth.priority = place.closingSoon == true ? 250 : 999
Ответ 3
UIStackView
- это, вероятно, путь для iOS 9+. Мало того, что он обрабатывает скрытый вид, он также удалит дополнительные интервалы и поля, если они настроены правильно.
Ответ 4
В этом случае я сопоставляю высоту метки Author с соответствующим IBOutlet:
@property (retain, nonatomic) IBOutlet NSLayoutConstraint* authorLabelHeight;
и когда я устанавливаю высоту ограничения на 0.0f, мы сохраняем "заполнение", потому что высота кнопки воспроизведения позволяет это.
cell.authorLabelHeight.constant = 0; //Hide
cell.authorLabelHeight.constant = 44; //Show
![]()
Ответ 5
Подкласс: просмотр и переопределение func intrinsicContentSize() -> CGSize
. Просто верните CGSizeZero
, если представление скрыто.
Ответ 6
Я создаю категорию, чтобы легко обновлять ограничения:
[myView1 hideByHeight:YES];
Ответить здесь:
Скрыть автозапуск UIView: как получить существующий NSLayoutConstraint для обновления этого
![enter image description here]()
Ответ 7
Я удивлен, что для этого желаемого поведения нет более элегантного подхода UIKit
. Похоже, что очень часто хочется быть в состоянии сделать.
Так как связывание ограничений с IBOutlets
и установка их констант на 0
чувствовали yucky (и вызвали предупреждения NSLayoutConstraint
, когда у вашего представления были subviews), я решил создать расширение, которое дает простой, устойчивый подход к скрытию/показу UIView
с ограничениями автоматического макета
Он просто скрывает представление и удаляет внешние ограничения. Когда вы снова показываете представление, он добавляет ограничения обратно. Единственное предостережение в том, что вам нужно будет указать гибкие ограничения переключения при переключении на другие виды.
Edit
Этот ответ ориентирован на iOS 8.4 и ниже. В iOS 9 просто используйте подход UIStackView
.
Ответ 8
Я только узнал, что для того, чтобы UILabel не занимал места, вам нужно скрыть его и установить его текст в пустую строку. (iOS 9)
Зная, что этот факт/ошибка может помочь некоторым людям упростить их макеты, возможно, даже из исходного вопроса, поэтому я решил, что опубликую его.
Ответ 9
Лучшая практика заключается в том, что, когда все имеет правильные ограничения макета, добавьте высоту или ограничение, в зависимости от того, как вы хотите, чтобы окружающие представления перемещались и связывали ограничение в свойство IBOutlet
.
Убедитесь, что ваши свойства strong
в коде вам просто нужно установить константу в 0 и активировать, чтобы скрыть содержимое, или деактивировать, чтобы показать содержимое.
Это лучше, чем испортить постоянную ценность, сохраняя-восстанавливая ее.
Не забудьте позже позвонить layoutIfNeeded
.
Если спрятанный контент сгруппирован, лучше всего поместить все в представление и добавить ограничения для этого представления
@property (strong, nonatomic) IBOutlet UIView *myContainer;
@property (strong, nonatomic) IBOutlet NSLayoutConstraint *myContainerHeight; //should be strong!!
-(void) showContainer
{
self.myContainerHeight.active = NO;
self.myContainer.hidden = NO;
[self.view layoutIfNeeded];
}
-(void) hideContainer
{
self.myContainerHeight.active = YES;
self.myContainerHeight.constant = 0.0f;
self.myContainer.hidden = YES;
[self.view layoutIfNeeded];
}
Как только у вас есть настройки, вы можете протестировать его в IntefaceBuilder, установив ограничение на 0, а затем обратно на исходное значение. Не забудьте проверить приоритеты других ограничений, поэтому, когда скрыты, конфликт вообще отсутствует. другой способ проверить это, чтобы поставить его в 0 и установить приоритет 0, но вы не должны забывать восстановить его с наивысшим приоритетом снова.
Ответ 10
Я также предоставил свое решение, чтобы предложить разнообразие.) Я думаю, что создание розетки для каждой позиции ширины/высоты плюс для интервала просто смешно и взрывает код, возможные ошибки и количество осложнений.
Мой метод удаляет все представления (в моем случае экземпляры UIImageView), выбирает, какие из них нужно добавить назад, и в цикле он добавляет назад каждый и создает новые ограничения. Это действительно просто, пожалуйста, пройдите. Вот мой быстрый и грязный код для этого:
// remove all views
[self.twitterImageView removeFromSuperview];
[self.localQuestionImageView removeFromSuperview];
// self.recipients always has to be present
NSMutableArray *items;
items = [@[self.recipients] mutableCopy];
// optionally add the twitter image
if (self.question.sharedOnTwitter.boolValue) {
[items addObject:self.twitterImageView];
}
// optionally add the location image
if (self.question.isLocal) {
[items addObject:self.localQuestionImageView];
}
UIView *previousItem;
UIView *currentItem;
previousItem = items[0];
[self.contentView addSubview:previousItem];
// now loop through, add the items and the constraints
for (int i = 1; i < items.count; i++) {
previousItem = items[i - 1];
currentItem = items[i];
[self.contentView addSubview:currentItem];
[currentItem mas_remakeConstraints:^(MASConstraintMaker *make) {
make.centerY.equalTo(previousItem.mas_centerY);
make.right.equalTo(previousItem.mas_left).offset(-5);
}];
}
// here I just connect the left-most UILabel to the last UIView in the list, whichever that was
previousItem = items.lastObject;
[self.userName mas_remakeConstraints:^(MASConstraintMaker *make) {
make.right.equalTo(previousItem.mas_left);
make.leading.equalTo(self.text.mas_leading);
make.centerY.equalTo(self.attachmentIndicator.mas_centerY);;
}];
Я получаю чистый, согласованный макет и интервал. Мой код использует масонство, я настоятельно рекомендую: https://github.com/SnapKit/Masonry
Ответ 11
Мой предпочтительный метод очень похож на предложенный Хорхе Аримани.
Я предпочитаю создавать несколько ограничений. Сначала создайте свои ограничения, когда будет видна вторая метка. Создайте выход для ограничения между кнопкой и 2-й меткой (если вы используете objc, убедитесь, что она сильная). Это ограничение определяет высоту между кнопкой и второй меткой, когда она видна.
Затем создайте другое ограничение, определяющее высоту между кнопкой и верхней меткой, когда вторая кнопка скрыта. Создайте выход во второе ограничение и убедитесь, что у этого выхода есть сильный указатель. Затем снимите флажок installed
в построителе интерфейса и убедитесь, что первый приоритет ограничения ниже, чем этот второй приоритет ограничений.
Наконец, когда вы скрываете вторую метку, переключите свойство .isActive
этих ограничений и вызовите setNeedsDisplay()
И что это, никаких Магических чисел, нет математики, если у вас есть несколько ограничений, чтобы включать и выключать, вы можете даже использовать коллекции выходных, чтобы держать их организованными по состоянию. (AKA сохраняет все скрытые ограничения в одном OutletCollection и не скрытые в другом и просто перебирает каждую коллекцию, переключая их .ISActive статус).
Я знаю, что Райан Романчук сказал, что он не хочет использовать множественные ограничения, но я чувствую, что это не micromanage-y, и проще, чем динамически создавать представления и ограничения программным образом (вот что, по моему мнению, он хотел избегайте, если я правильно прочитаю вопрос).
Я создал простой пример, надеюсь, он будет полезен...
import UIKit
class ViewController: UIViewController {
@IBOutlet var ToBeHiddenLabel: UILabel!
@IBOutlet var hiddenConstraint: NSLayoutConstraint!
@IBOutlet var notHiddenConstraint: NSLayoutConstraint!
@IBAction func HideMiddleButton(_ sender: Any) {
ToBeHiddenLabel.isHidden = !ToBeHiddenLabel.isHidden
notHiddenConstraint.isActive = !notHiddenConstraint.isActive
hiddenConstraint.isActive = !hiddenConstraint.isActive
self.view.setNeedsDisplay()
}
}
![введите описание изображения здесь]()