AutoLayout со скрытыми UIViews?

Мне кажется, что это довольно распространенная парадигма - показывать / скрывать UIViews, чаще всего UILabels, в зависимости от бизнес-логики. Мой вопрос: как лучше всего использовать AutoLayout для ответа на скрытые представления, как если бы их фрейм был 0x0. Вот пример динамического списка из 1-3 функций.

Список динамических функций

Прямо сейчас у меня есть верхнее пространство 10 пикселей от кнопки до последней метки, которое, очевидно, не будет сдвигаться вверх, когда метка скрыта. На данный момент я создал выход для этого ограничения и изменил константу в зависимости от того, сколько ярлыков я отображаю. Это, очевидно, немного взломано, поскольку я использую отрицательные постоянные значения, чтобы нажимать кнопку над скрытыми кадрами. Это также плохо, потому что он не ограничен фактическими элементами макета, а просто скрытыми статическими вычислениями, основанными на известных высотах / заполнении других элементов, и, очевидно, борется с тем, для чего был создан AutoLayout.

Я, очевидно, мог бы просто создать новые ограничения в зависимости от моих динамических меток, но это много микроменеджмента и много многословия, чтобы попытаться просто свернуть некоторые пробелы. Есть ли подходы лучше? Изменить размер кадра 0,0 и позволить AutoLayout делать свое дело без манипуляций с ограничениями? Полностью удалить просмотры?

Честно говоря, простое изменение константы из контекста скрытого представления требует единственной строчки кода с простыми вычислениями. Воссоздание новых ограничений с помощью constraintWithItem:attribute:relatedBy:toItem:attribute:multiplier:constant: кажется таким трудным.

Изменить, февраль 2018 г.: см. ответ Бена с помощью UIStackViews


person Ryan Romanchuk    schedule 24.10.2013    source источник
comment
Спасибо Райану за этот вопрос. Я сходил с ума, что делать с делами, как вы просили. Каждый раз, когда я просматриваю учебник по автопрокладке, большинство из них говорит, что обратитесь к сайту с учебниками raywenderlich, который мне немного сложно разобрать.   -  person Nassif    schedule 07.02.2014


Ответы (13)


UIStackView, вероятно, лучше всего подходит для iOS 9+. Он не только обрабатывает скрытый вид, но и удаляет дополнительные интервалы и поля, если они настроены правильно.

person Ben Packard    schedule 03.10.2015
comment
Будьте осторожны при использовании UIStackView в UITableViewCell. Для меня, как только представление стало очень сложным, прокрутка стала прерывистой, когда она заикалась каждый раз, когда ячейка прокручивалась внутрь / наружу. Под обложками StackView добавляет / удаляет ограничения, и это не обязательно будет достаточно эффективным для плавной прокрутки. - person Kento; 16.02.2016
comment
Было бы неплохо привести небольшой пример того, как с этим справляется представление стека. - person lostintranslation; 09.09.2016
comment
@Kento, это все еще проблема с iOS 10? - person Crashalot; 31.10.2017
comment
Пять лет спустя ... следует ли мне обновить и установить это как принятый ответ? Теперь он доступен в течение трех выпусков. - person Ryan Romanchuk; 13.02.2018
comment
@RyanRomanchuk Вы должны, это определенно лучший ответ на данный момент. Спасибо, Бен! - person Duncan Luk; 30.07.2018
comment
Я использую UIStackView, но не могу правильно работать с полями отображения / скрытия. Пожалуйста, проверьте мой вопрос и предложите что-нибудь. Пожалуйста, перейдите по этой ссылке stackoverflow.com/questions/51720099/ - person iamVishal16; 08.08.2018
comment
@Kento: BoxView похож на UIStackView, он совместим с чем угодно и имеет больше вариантов компоновки. - person Vladimir; 28.06.2020

Мое личное предпочтение для отображения / скрытия представлений - создать IBOutlet с соответствующими ограничениями ширины или высоты.

Затем я обновляю значение constant на 0, чтобы скрыть, или любое другое значение, которое должно быть отображено.

Большим преимуществом этого метода является сохранение относительных ограничений. Например, предположим, что у вас есть вид A и вид B с горизонтальным зазором x. Когда ширина вида A constant установлена ​​на 0.f, тогда вид B переместится влево, чтобы заполнить это пространство.

Нет необходимости добавлять или удалять ограничения, а это сложная операция. Просто обновите constant ограничения.

person Max MacLeod    schedule 25.10.2013
comment
Мне очень нравится этот ответ. Итак, в основном, если я правильно понимаю, установите фиксированные ограничения по высоте или весу, создайте IBOutlet для этих ограничений, и если это представление скрыто, просто измените значение на константу 0.f, а остальные элементы должны встать на свои места? - person Ryan Romanchuk; 25.10.2013
comment
точно. Пока - в этом примере - вы позиционируете вид B справа от вида A, используя горизонтальный зазор. Я использую эту технику все время, и она очень хорошо работает - person Max MacLeod; 25.10.2013
comment
@MaxMacLeod Что, если метка потенциально многострочная? В этом случае ограничение по высоте было бы неуместным, верно? - person markdorison; 30.01.2014
comment
@markdorison Привет, Марк, я действительно видел твой пост в твиттере. Я так понимаю, в вашем случае метка переменной высоты? Это должно работать нормально, но высота, конечно же, должна соответствовать содержимому с помощью boundingRectWithSize. Интересно, нужно ли вам предварительно рассчитать высоту для каждой ячейки. - person Max MacLeod; 30.01.2014
comment
@MaxMacLeod Просто чтобы убедиться: если промежуток между двумя представлениями равен x, для того, чтобы представление B начиналось с позиции представления A, мы также должны изменить константу ограничения промежутка на 0, верно? - person kernix; 07.03.2014
comment
@kernix да, если между двумя представлениями должно быть ограничение по горизонтали. константа 0, конечно, означает отсутствие пробелов. Таким образом, если скрыть вид A, установив для него ширину 0, вид B сдвинется влево, чтобы он оказался на одном уровне с контейнером. Кстати, спасибо за голоса за! - person Max MacLeod; 07.03.2014
comment
@MaxMacLeod Спасибо за ответ. Я пробовал сделать это с помощью UIView, который содержит другие представления, но подвиды этого вида не скрываются, когда я устанавливаю ограничение по высоте на 0. Предполагается ли, что это решение работает с представлениями, содержащими подвиды? Спасибо! - person shaunlim; 28.08.2014
comment
Также был бы признателен за решение, которое работает для UIView, содержащего другие представления ... - person Mark Gibaud; 23.10.2014
comment
Я думаю, что ваше решение сработает для моей проблемы. Если бы это было так, вы могли бы привести пример кода, поскольку я новичок в разработке IOS, и ограничения меня действительно смущают. Это сообщение stackoverflow.com/questions/27347937/ @MaxMacLeod - person Timo Cengiz; 09.12.2014
comment
Итак, вопрос по этому поводу, поскольку это действительно автоматический макет, как бы вы узнали, для чего установить высоту? Хотели бы вы, чтобы элемент управления сбрасывал высоту, и если да, то как? - person Rob Bonner; 07.09.2015
comment
@RobBonner отличный вопрос. Метка может иметь размеры, автоматически устанавливаемые с внутренним размером содержимого. Однако в этом случае это не сработает, так как у вас будет явное ограничение по высоте. Таким образом, вы собираетесь либо жестко запрограммировать высоту в Xib, а затем указать в контроллере представления, либо установить полностью программно в контроллере представления. - person Max MacLeod; 07.09.2015
comment
Спасибо @Max MacLeod Но у меня небольшая проблема. У моего представления есть вертикальные поля, поэтому даже если я добавлю ограничение нулевой высоты с высоким или обязательным приоритетом, я вижу ошибку в консоли, потому что эти ограничения не могут быть выполнены одновременно. - person Ricardo; 10.12.2015

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

У меня есть решение получше.

Идея состоит в том, чтобы установить для правила высоты 0 высокий приоритет, когда мы скрываем элемент, чтобы он не занимал места для автоматического размещения.

Вот как это сделать:

1. установите ширину (или высоту) 0 в конструкторе интерфейсов с низким приоритетом.

правило установки ширины 0

Интерфейсный разработчик не кричит о конфликтах из-за низкого приоритета. Проверьте поведение высоты, временно установив приоритет 999 (1000 запрещено изменять программно, поэтому мы не будем его использовать). Конструктор интерфейсов, вероятно, теперь будет кричать о конфликтующих ограничениях. Вы можете исправить это, установив приоритеты для связанных объектов на 900 или около того.

2. Добавьте розетку, чтобы можно было изменить приоритет ограничения ширины в коде:

выходное соединение

3. Настройте приоритет при скрытии элемента:

cell.alertTimingView.hidden    = place.closingSoon != true
cell.alertTimingWidth.priority = place.closingSoon == true ? 250 : 999
person SimplGy    schedule 10.06.2015
comment
@SimplGy, пожалуйста, скажите мне, что это за место .closingSoon? - person Niharika; 17.02.2017
comment
Это не часть общего решения, @Niharika. Это просто то, что было бизнес-правилом в моем случае (покажите вид, если ресторан закрывается / не скоро закрывается). - person SimplGy; 19.02.2017

В этом случае я сопоставляю высоту метки Author с соответствующим IBOutlet:

@property (retain, nonatomic) IBOutlet NSLayoutConstraint* authorLabelHeight;

и когда я устанавливаю высоту ограничения на 0,0f, мы сохраняем «отступ», потому что высота кнопки Play позволяет это.

cell.authorLabelHeight.constant = 0; //Hide 
cell.authorLabelHeight.constant = 44; //Show

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

person Mitul Marsoniya    schedule 01.12.2015

Здесь много решений, но мой обычный подход снова другой :)

Установите два набора ограничений, подобных ответу Хорхе Аримани и TMin:

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

Все три отмеченных ограничения имеют одинаковое значение для константы. Ограничения, помеченные A1 и A2, имеют свой Priority, установленный на 500, в то время как ограничение, отмеченное B, имеет свой Priority, установленный на 250 (или UILayoutProperty.defaultLow в коде).

Подключите ограничение B к IBOutlet. Затем, когда вы скрываете элемент, вам просто нужно установить приоритет ограничения на высокий (750):

constraintB.priority = .defaultHigh

Но когда элемент виден, установите приоритет обратно на низкий (250):

constraintB.priority = .defaultLow

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

person SeanR    schedule 13.02.2018
comment
Это дает преимущества в отсутствии повторяющихся значений в раскадровке и коде, таких как высота / ширина элемента. Очень элегантно. - person Robin Daugherty; 19.05.2018
comment
Спасибо!! Это помогло мне - person Parth Barot; 18.03.2019
comment
Отличный подход, спасибо - person Basil; 23.04.2019

Создайте подкласс представления и переопределите func intrinsicContentSize() -> CGSize. Просто верните CGSizeZero, если представление скрыто.

person Daniel    schedule 20.10.2015
comment
Отлично. Однако некоторым элементам пользовательского интерфейса нужен триггер для invalidateIntrinsicContentSize. Вы можете сделать это в переопределенном setHidden. - person DrMickeyLauer; 07.03.2016

Я только что узнал, что для того, чтобы UILabel не занимал места, вы должны скрыть его И установить его текст в пустую строку. (iOS 9)

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

person Matt Koala    schedule 08.12.2015
comment
Не думаю, что вам нужно это скрывать. Достаточно установить его текст в виде пустой строки. - person Rob VS; 11.07.2016

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

Поскольку подключение ограничений к IBOutlets и установка их констант на 0 показалось неприятным (и вызывало NSLayoutConstraint предупреждения, когда ваше представление имело подвиды), я решил создать расширение, которое дает простой подход с отслеживанием состояния для скрытия / отображения UIView с ограничениями автоматического макета

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

Изменить. Этот ответ предназначен для iOS 8.4 и более ранних версий. В iOS 9 просто используйте подход UIStackView.

person Albert Bori    schedule 19.05.2015
comment
Это мой предпочтительный подход. Это значительное улучшение по сравнению с другими ответами здесь, поскольку оно не просто сводит представление к нулевому размеру. Подход сквоша будет иметь проблемы с чем угодно, кроме довольно тривиальных выплат. Примером этого является большой разрыв, где скрытый элемент находится в этом другом ответе. И вы можете закончить с нарушениями ограничений, как уже упоминалось. - person Benjohn; 10.11.2015
comment
@DanielSchlaug Спасибо, что указали на это. Я обновил лицензию на MIT. Однако мне нужно мысленно давать пять каждый раз, когда кто-то реализует это решение. - person Albert Bori; 15.01.2016

Лучше всего, когда все имеет правильные ограничения макета, добавьте высоту или с ограничением, в зависимости от того, как вы хотите, чтобы окружающие виды перемещались, и подключите ограничение к свойству 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, но вы не должны забывать снова восстановить его на наивысший приоритет.

person Jorge Arimany    schedule 05.03.2016
comment
Это связано с тем, что ограничение и представление не всегда могут быть сохранены иерархией представлений, поэтому, если вы деактивируете ограничение и скроете представление, вы все равно сохраните их, чтобы отобразить их снова, независимо от того, что ваша иерархия представлений решила сохранить или нет. - person Jorge Arimany; 30.05.2016

Я создаю категорию, чтобы легко обновлять ограничения:

[myView1 hideByHeight:YES];

Ответьте здесь: Скрыть UIView автоматического раскладки: Как получить существующий NSLayoutConstraint для обновления этого

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

person Damien Romito    schedule 13.03.2014
comment
К сожалению, вы получаете двойной интервал там, где указывает желтая стрелка. - person Zoltán; 02.11.2015
comment
Думаю, как и в случае с другими решениями выше. Совсем не идеально. - person Benjohn; 10.11.2015

Мой предпочтительный метод очень похож на метод, предложенный Хорхе Аримани.

Я предпочитаю создавать несколько ограничений. Сначала создайте ограничения, когда будет видна вторая метка. Создайте выход для ограничения между кнопкой и второй меткой (если вы используете objc, убедитесь, что он сильный). Это ограничение определяет высоту между кнопкой и второй меткой, когда она видна.

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

Наконец, когда вы скроете вторую метку, переключите свойство .isActive этих ограничений и вызовите setNeedsDisplay()

И все, никаких магических чисел, никакой математики, если у вас есть несколько ограничений для включения и выключения, вы даже можете использовать коллекции розеток, чтобы упорядочить их по штатам. (AKA хранит все скрытые ограничения в одной OutletCollection, а не скрытые в другой и просто перебирайте каждую коллекцию, переключая их статус .isActive).

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

Я создал простой пример, надеюсь, он будет полезен ...

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()
    }
}

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

person TMin    schedule 21.03.2017

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

Мой метод удаляет все представления (в моем случае экземпляры 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

person Zoltán    schedule 02.11.2015

Попробуйте BoxView, он делает динамический макет кратким и читаемым.
В вашем случае это:

    boxView.optItems = [
        firstLabel.boxed.useIf(isFirstLabelShown),
        secondLabel.boxed.useIf(isSecondLabelShown),
        button.boxed
    ]
person Vladimir    schedule 28.06.2020