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


145

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

Dynamic features list

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

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

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

Редактировать февраль 2018: Смотрите ответ Бен с UIStackView сек

  0

Спасибо Райан за этот вопрос. Я с ума сходил, что делать для дел, как вы просили. Каждый раз, когда я просматриваю учебное пособие по автозапуску, большинство из них говорит, что ссылается на сайт учебника raywenderlich, который мне немного сложно понять. 07 фев. 142014-02-07 11:56:00

225

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

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

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

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

  0

Мне очень нравится этот ответ. Таким образом, в принципе, если я правильно понимаю, устанавливаю фиксированные ограничения высоты или веса, создайте IBOutlet для этих ограничений, и если это представление скрыто, просто измените на константу 0.f, а остальные элементы должны встать на свои места? 25 окт. 132013-10-25 11:23:59

+1

точно. Пока - в этом примере - вы позиционируете вид B справа от вида A, используя горизонтальный зазор. Я использую эту технику все время, и она очень хорошо работает 25 окт. 132013-10-25 11:47:30

  0

@MaxMacLeod Что делать, если метка потенциально многострочная? В этом случае ограничение высоты не было бы подходящим, верно? 29 янв. 142014-01-29 23:10:43

  0

@markdorison Привет, Марк, я видел ваше сообщение на твиттере. Я полагаю, что в вашем случае метка является переменной высотой? Это должно работать нормально, но высота, конечно же, должна соответствовать содержимому с помощью boundingRectWithSize. Интересно, вам нужно будет предварительно вычислить высоты для каждой ячейки. 30 янв. 142014-01-30 08:59:30

+7

@MaxMacLeod Просто, чтобы убедиться: если пробел между двумя представлениями равен x, то для того, чтобы представление B начиналось с позиции зрения, мы также должны изменить константу ограничения разрыва на 0 тоже, правильно? 07 мар. 142014-03-07 09:14:09

+1

@kernix да, если между двумя представлениями должно быть ограничение по горизонтали. постоянная 0, конечно, означает отсутствие зазора. Таким образом, скрывая представление A, установив его ширину на 0, вы перемещаете вид B влево, чтобы скрыть контейнер. Btw благодарит за голосование! 07 мар. 142014-03-07 09:34:14

+2

@MaxMacLeod Спасибо за ваш ответ. Я попытался сделать это с UIView, который содержит другие представления, но subviews этого представления не скрыты, когда я устанавливаю ограничение по высоте на 0. Предполагается ли это решение работать с представлениями, содержащими subviews? Благодаря! 27 авг. 142014-08-27 23:49:46

+5

Также оценил бы решение, которое работает для UIView, содержащего другие представления ... 23 окт. 142014-10-23 13:11:23

  0

Я думаю, что ваше решение будет работать для моей проблемы. Если бы вы могли представить пример кода, так как im new для разработки IOS и ограничения действительно путают меня. Это сообщение http://stackoverflow.com/questions/27347937/changing-position-of-a-uilabel-for-a-custo @MaxMacLeod 09 дек. 142014-12-09 17:49:04

  0

Так что вопрос на этом, так как это действительно автоматический макет, как бы вы знаете, как установить высоту? Хотите ли вы сбросить контроль, это высота, и если да, то как? 06 сен. 152015-09-06 23:50:11

  0

@RobBonner большой вопрос. Метка может иметь размеры, автоматически устанавливаемые с внутренним размером содержимого. Однако это не сработает в этом случае, поскольку у вас будет явное ограничение по высоте. Таким образом, вы или будете иметь высоту, жестко закодированную в Xib, - затем отметили в контроллере представления - или полностью программно настроили в контроллере представления. 07 сен. 152015-09-07 05:54:44

  0

Спасибо @Max MacLeod Но у меня небольшая проблема. У моего представления есть вертикальные поля, поэтому, даже если я добавлю ограничение с нулевой высотой с высоким или требуемым приоритетом, я вижу ошибку в консоли, потому что эти ограничения не могут быть выполнены одновременно. 10 дек. 152015-12-10 12:49:41


3

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

[myView1 hideByHeight:YES]; 

Ответ здесь: Hide autolayout UIView : How to get existing NSLayoutConstraint to update this one

enter image description here

+7

К сожалению, вы получаете двойной интервал там, где указывает желтая стрелка. 02 ноя. 152015-11-02 19:28:22

  0

Как и другие решения выше, я думаю. Не идеально. 10 ноя. 152015-11-10 16:58:27


4

Я удивлен, что не существует более элегантный подход обеспечивает UIKit для этого желаемое поведение. Похоже, очень хочется, чтобы вы могли это сделать.

Поскольку подключение ограничений к IBOutlets и устанавливать константы 0 почувствовал противный (и вызвал NSLayoutConstraint предупреждения, когда ваше мнение было подвидов), я решил создать расширение, которое дает simple, stateful approach to hiding/showing a UIView that has Auto Layout constraints

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

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

+1

Это мой предпочтительный подход. Это значительное улучшение в других ответах здесь, поскольку это не просто сквозит представление до нулевого размера. Подход к сквошу будет иметь проблемы с чем-либо, но довольно тривиальные выплаты. Примером этого является большой пробел, в котором скрытый элемент находится в [этом другом ответе] (http://stackoverflow.com/a/22386997/2547229). И, как уже упоминалось, вы можете столкнуться с нарушениями ограничений. 10 ноя. 152015-11-10 17:04:20

  0

@ DanielSchlaug Спасибо, что указали это. Я обновил лицензию на MIT. Тем не менее, я требую умственного пять-пять каждый раз, когда кто-то реализует это решение. 15 янв. 162016-01-15 17:12:35


90

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

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

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

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

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

setting width 0 rule

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

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

outlet connection

3. Отрегулируйте приоритет, когда вы скрываете свой элемент:

cell.alertTimingView.hidden = place.closingSoon != true 
cell.alertTimingWidth.priority = place.closingSoon == true ? 250 : 999 
  0

@SimplGy не могли бы вы рассказать мне, что это за место. 17 фев. 172017-02-17 06:09:14

  0

Это не часть общего решения, @Niharika. Это просто бизнес-правило в моем случае (покажите представление, скоро ли ресторан закрывается). 19 фев. 172017-02-19 00:35:58


67

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

+5

Остерегайтесь использования UIStackView в UITableViewCell. Для меня, когда представление получилось очень сложным, прокрутка начала становиться изменчивой, когда она заикалась каждый раз, когда ячейка была прокручена в/из. Под обложками StackView добавляет/удаляет ограничения, и это не обязательно будет достаточно эффективно для плавной прокрутки. 16 фев. 162016-02-16 18:31:07

+5

Было бы неплохо дать небольшой пример того, как выглядит стек. 09 сен. 162016-09-09 19:47:14

  0

@ Kento это все еще проблема на iOS 10? 31 окт. 172017-10-31 20:31:00

+2

Пять лет спустя ... следует ли обновить и установить это как принятый ответ? Теперь он доступен для трех циклов выпуска. 13 фев. 182018-02-13 06:02:20


5

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

+2

Это замечательно. Некоторым элементам пользовательского интерфейса нужен триггер для 'invalidateIntrinsicContentSize'. Вы можете сделать это в переопределенном 'setHidden'. 07 мар. 162016-03-07 09:23:19


0

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

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

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

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

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

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

enter image description here


3

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

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

  0

Я не думаю, что вам нужно скрыть это. Достаточно указать свой текст на пустую строку. 11 июл. 162016-07-11 12:27:41


3

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

+1

Это связано с тем, что ограничение и представление не всегда сохраняются иерархией представлений, поэтому, если вы деактивируете ограничение и спрячете представление, которое вы все еще сохраняете, чтобы отобразить их снова, независимо от того, что ваша иерархия представлений принимает решение сохранить или нет , 30 май. 162016-05-30 09:06:48


2

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

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

enter image description here


0

Там много решений здесь, но мой обычный подход отличается снова :)

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

enter image description here

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

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

constraintB.priority = .defaultHigh

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

constraintB.priority = .defaultLow

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