Когда я могу активировать / деактивировать ограничения макета?

104

Я установил несколько наборов ограничений в IB, и я хотел бы программно переключаться между ними в зависимости от некоторого состояния. Есть constraintsAколлекция розеток, все из которых помечены как установленные из IB, и constraintsBколлекция розеток, все из которых удалены в IB.

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

NSLayoutConstraint.deactivateConstraints(constraintsA)
NSLayoutConstraint.activateConstraints(constraintsB)

Но ... я не могу понять, когда это сделать. Кажется, я смогу сделать это один раз viewDidLoad, но я не могу заставить это работать. Я пробовал позвонить view.updateConstraints()и view.layoutSubviews()после установки ограничений, но безуспешно.

Я обнаружил, что если я установлю ограничения, viewDidLayoutSubviewsвсе будет работать так, как ожидалось. Думаю, я хотел бы знать две вещи ...

  1. Почему у меня такое поведение?
  2. Можно ли активировать / деактивировать ограничения из viewDidLoad?
tybro0103
источник
2
Вы имеете в виду, что deactivateConstraints и activateConstraints работали в viewWillLayoutSubviews? Я пробовал это, и это не сработало ни там, ни в viewDidLoad. Это вроде работало в viewDidAppear; представление появилось там, где должны были быть помещены новые ограничения, но если я повернул в альбомную ориентацию, представление вернулось в положение, определенное ограничениями, установленными в IB (и осталось там, когда я повернулся обратно к портрету). Регистрируя ограничения, показывались правильные (те, которые только что активированы). Мне это кажется ошибкой.
rdelmar
1
Да, они были действительными (они работали в viewDidAppear), и нет необходимости вызывать super, потому что нет реализации по умолчанию для viewWillLayoutSubviews (я все равно пробовал это с вызовом super, но это не имело никакого значения).
rdelmar
1
@rdelmar Только что получил возможность протестировать больше ... Я могу проверить, что действительно получил то же поведение, что вы описали ... сначала работает в viewDidAppear, но затем возвращается в исходное состояние при повороте.
tybro0103
3
Очевидно, вы не можете пометить ограничения как не установленные в IB для этой цели. Нашел эту информацию здесь: stackoverflow.com/questions/27663249/… и решил проблему для меня.
Стефан
1
Мои ограничения были реализованы так же, как описано в вопросе, за исключением того, что я активировал / деактивировал некоторые из них в viewDidAppear. Это сработало, но вы могли видеть, что элементы быстро меняют положение (небольшая, но нежелательная проблема). Внесение изменений в viewWillAppear или viewDidLoad не помогло. Но, прочитав этот вопрос, я попытался внести это изменение в viewDidLayoutSubviews. Это сработало, и изменение позиции больше не видно пользователю. (Это также работало во viewWillLayoutSubviews). Так что спасибо за совет!
Peacetype

Ответы:

186

Активирую и деактивирую NSLayoutConstraintsв viewDidLoad, и у меня с этим проблем нет. Так что это работает. Должна быть разница в настройке вашего приложения и моего :-)

Я просто опишу свою настройку - может быть, это поможет вам:

  1. Я установил @IBOutletsвсе ограничения, которые мне нужно активировать / деактивировать.
  2. В ViewController, я сохраняю ограничения в свойствах класса, которые не являются слабыми. Причина этого в том, что я обнаружил, что после деактивации ограничения я не мог повторно активировать его - оно было равно нулю. Значит, при деактивации вроде удаляется.
  3. Я не использую, NSLayoutConstraint.deactivate/activateкак вы, я использую constraint.active = YES/ NOвместо.
  4. После установки ограничений я звоню view.layoutIfNeeded().
Иоахим Бёггильд
источник
132
"сохранить ограничения в свойствах класса, которые не являются слабыми" Вы сэкономили мне много времени, спасибо!
OpenUserX03
10
«Я сохраняю ограничения в свойствах класса, которые не являются слабыми»: это избавило меня от множества страданий. Я не знал, что вызываю селектор для объекта nil. Спасибо!!
static0886 07
4
Важно отметить, что «неактивные» ограничения не игнорируются автоматическим макетом, они удаляются. Активация / деактивация ограничений фактически добавляет и удаляет их. Потратил некоторое время на отладку конфликтующего автоматического макета после того, как я добавил ограничения, которые я ранее установил, .active = falseожидая, что они будут проигнорированы, пока я не установлю их в активное состояние.
lbarbosa
1
сохраните ограничения в свойствах класса, которые не являются слабыми, хорошо, это экономит много времени, без этого я получал смешанные результаты. Спасибо чувак!
MegaManX 04
3
В документе Apple говорится: Активация или деактивация ограничения вызывает addConstraint ( :) и removeConstraint ( :) в представлении, которое является ближайшим общим предком элементов, управляемых этим ограничением. Используйте это свойство вместо прямого вызова addConstraint ( :) или removeConstraint ( :). Таким образом, кажется, что когда ограничение деактивируется, оно удаляется, и тогда не остается никаких сильных ссылок на ограничение, если только IBOutlet не является сильным. Следовательно, ограничение удалено. ИМХО, это почти ошибка или, по крайней мере, очень неожиданное поведение.
Олле Рааб
52

Может быть, вы могли бы проверить свой @properties, заменить weakнаstrong .

Иногда это потому , что он active = NOустановлен self.yourConstraint = nil, чтобы вы не могли использовать его self.yourConstraintснова.

Лео
источник
5
Как указано в Swift Language Guide , свойства по умолчанию являются сильными, поэтому вы также можете просто удалить, weakи это будет сделано.
Джонатан Кабрера
30
override func viewDidLayoutSubviews() {
// do it here, after constraints have been materialized
}
Тони Свифтгай
источник
1
Поскольку мой контроллер представления является контроллером дочернего представления - кажется, что выполнение его в "didLayoutSubviews" - единственный способ, который сработал !! К вашему сведению.
TalL
Это единственный
Юнус Эрен Гюзель
@TalL Вы имели в виду ограничения для самого контроллера дочернего представления или его подпредставлений?
Стефан
это лучший
ACAkgul
14

Я считаю, что проблема, с которой вы столкнулись, связана с тем, что ограничения не добавляются к их представлениям до вызова AFTER viewDidLoad(). У вас есть несколько вариантов:

A) Вы можете подключить свои ограничения макета к IBOutlet и получить к ним доступ в своем коде по этим ссылкам. Поскольку розетки подключаются перед viewDidLoad()запуском, ограничения должны быть доступны, и вы можете продолжать активировать и деактивировать их там.

B) Если вы хотите использовать constraints()функцию UIView для доступа к различным ограничениям, вы должны дождаться viewDidLayoutSubviews()начала и сделать это там, поскольку это первая точка после создания контроллера представления из пера, в котором будут установлены какие-либо ограничения. Не забудьте позвонить, layoutIfNeeded()когда закончите. У этого есть недостаток, заключающийся в том, что проход макета будет выполняться дважды, если есть какие-либо изменения, которые нужно применить, и вы должны убедиться, что нет возможности запуска бесконечного цикла.

Небольшое предупреждение: отключенные ограничения НЕ возвращаются constraints() методом! Это означает, что если вы ДЕЙСТВИТЕЛЬНО отключите ограничение с намерением снова включить его позже, вам нужно будет сохранить ссылку на него.

C) Вы можете забыть о раскадровке и вместо этого добавить свои ограничения вручную. Поскольку вы делаете это, viewDidLoad()я предполагаю, что намерение состоит в том, чтобы сделать это только один раз за все время существования объекта, а не изменять макет на лету, поэтому это должен быть приемлемый метод.

Ясень
источник
10

Вы также можете настроить priorityсвойство, чтобы «включить» и «отключить» их (например, значение 750 для включения и 250 для отключения). По какой-то причине изменение activeBOOL не повлияло на мой интерфейс. Нет необходимости в layoutIfNeededи может быть установлен и изменен в viewDidLoad или в любое время после этого.

user2387149
источник
Очень хорошее предложение. Изменение приоритета ограничений работает в viewWillTransition(to:, with:)или, viewWillLayoutSubviews()и вы можете оставить все свои альтернативные ограничения как «установленные» в раскадровке. Приоритет ограничения не может измениться с необязательного на обязательный, поэтому используйте значения ниже 1000. С другой стороны, активация (добавление) и деактивация (удаление) ограничений работает только в viewDidLayoutSubviews()и требует сохранения strong @IBOutletссылок на NSLayoutConstraint-s.
Гэри
«По какой-то причине изменение активного BOOL не повлияло на мой интерфейс». На основе здесь . Я думаю, вы не можете изменить ограничение с приоритетом 1000 во время выполнения. Если вы хотите отключить его, тогда вы должны установить начальный приоритет на 999 или ниже ....
Дорогая,
Я не согласен с этим утверждением, поскольку оно может привести к трудным для отладки проблемам и не дает ответа на вопрос. Установка приоритета на 250 не «деактивирует» ограничение, оно все равно будет иметь эффект и влиять на макет. Может показаться, что это «деактивирует» ограничение в большинстве случаев, но определенно не во всех случаях. (в частности, не тот случай, который заставил меня найти ответ на этот вопрос)
Tumata
Это также может привести к сбоям, например: «Изменение приоритета с обязательного на не установленного ограничения (или наоборот) не поддерживается. Вы передали приоритет 250, а существующий приоритет был 1000».
Картик Рамеш
8

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

-(void)viewWillLayoutSubviews{
    [super viewWillLayoutSubviews];

    self.myLittleConstraint.active = NO;
}

Имейте в виду, что это viewWillLayoutSubviewsможет быть вызвано несколько раз, так что никаких сложных вычислений, хорошо?

Примечание: если вы хотите отреагировать на некоторые из ограничений позже, всегда сохраняйте strongссылку на них.

Ласло
источник
2
Для меня единственный надежный способ - отрегулировать ограничения в viewDidLayoutSubviews(). В viewWillLayoutSubviews()моем случае настройка ограничений не работает.
петрсын 08
6

При создании представления по порядку вызываются следующие методы жизненного цикла:

  1. loadView
  2. viewDidLoad
  3. viewWillAppear
  4. viewWillLayoutSubviews
  5. viewDidLayoutSubviews
  6. viewDidAppear

Теперь к вашим вопросам.

  1. Почему у меня такое поведение?

Ответ: Потому что, когда вы пытаетесь установить ограничения для представлений, в viewDidLoadпредставлении нет своих границ, следовательно, ограничения не могут быть установлены. Только после viewDidLayoutSubviewsэтого границы представления окончательно определены.

  1. Можно ли активировать / деактивировать ограничения из viewDidLoad?

Ответ: Нет. Причина объяснена выше.

Сумит
источник
В своем описании жизненного цикла viewController вы говорили о том, как сначала загружается представление, а затем вызывается viewDidLoad. Однако вы также сказали, что представление не создается к моменту вызова viewDidLoad, это явно противоречие. Кроме того, вы можете проверить себя и увидеть, что представление было создано к моменту вызова viewDidLoad, потому что вы можете добавить подвиды в представление.
ABakerSmith
viewDidLoad должен быть в порядке, поскольку представления создаются и загружаются ... На самом деле, когда вы активируете ограничения, в основном сводится к производительности. Я предполагаю, что исходная проблема не была связана с тем, где были активированы ограничения. stackoverflow.com/questions/19387998/…
Гейб
@ABakerSmith, я отредактировал свой ответ, чтобы он был более ясным.
Sumeet
1

Я обнаружил, что до тех пор, пока вы устанавливаете ограничения в соответствии с нормой в переопределении - (void)updateConstraints(цель c), со strongссылкой на начальное значение используемых активных и неактивных ограничений. А в другом месте цикла просмотра деактивируйте и / или активируйте то, что вам нужно, а затем позвоните layoutIfNeeded, у вас не должно возникнуть проблем.

Главное - не постоянно повторно использовать переопределение updateConstraintsи разделять активации ограничений, если вы вызываете updateConstraints после первой инициализации и макета. После этого, кажется, имеет значение, где именно в цикле просмотра.

эллиотрок
источник