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


104

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

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

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

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

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

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

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 или в любое время после этого.


Очень хорошее предложение. Изменение приоритета ограничений работает в 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 после первой инициализации и макета. После этого, кажется, имеет значение, где именно в цикле просмотра.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.