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, если представление скрыто.

Ответ 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()
    }
}

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