Коли я можу активувати / відключити обмеження макета?


104

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

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

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

Але ... я не можу зрозуміти, коли це зробити. Здається, я повинен бути в змозі зробити це один раз viewDidLoad, але я не можу це працювати. Я намагався телефонувати view.updateConstraints()і view.layoutSubviews()після встановлення обмежень, але безрезультатно.

Я виявив, що якщо встановити обмеження, viewDidLayoutSubviewsвсе працює так, як очікувалося. Я думаю, я хотів би знати дві речі ...

  1. Чому я отримую таку поведінку?
  2. Чи можливо активувати / деактивувати обмеження з viewDidLoad?

2
Ви маєте на увазі деактивувати Обмеження та активуватиЗаганки, що працювали в viewWillLayoutSubviews? Я спробував це, і це не спрацювало там чи в viewDidLoad. Це свого роду працювало в viewDidAppear; вигляд з'явився там, де повинні бути встановлені нові обмеження, але якщо я повернувся до пейзажу, погляд повернувся до положення, визначеного обмеженнями, встановленими в ІБ (і залишився там, коли я повертався назад до портрета). Реєструючи обмеження, показали правильні (ті, що знову активовані). Це здається мені помилкою.
rdelmar

1
Так, вони були дійсними (вони працювали в viewDidAppear), і не потрібно викликати супер, оскільки немає за замовчуванням реалізації viewWillLayoutSubviews (я намагався це зробити з викликом super все одно, але це не мало значення).
rdelmar

1
@rdelmar Щойно отримав можливість пройти тестування більше ... Я можу перевірити, чи справді я мав таку саму поведінку, яку ви описали ... Спочатку працює у viewDidAppear, але потім повертається після обертання.
tybro0103

3
Мабуть, ви не можете позначити обмеження як такі, що не встановлені в IB для цієї мети. Знайшов цю інформацію тут: stackoverflow.com/questions/27663249/…, і це вирішило проблему для мене.
Стефан

1
У мене були обмеження, реалізовані так само, як описано в питанні, за винятком того, що я активував / деактивував деякі з них у viewDidAppear. Це спрацювало, але ви побачили, як елементи швидко змінюють позицію (незначна, але небажана проблема). Внесення змін у viewWillAppear або viewDidLoad не працювало. Але прочитавши це питання, я спробував внести цю зміну у viewDidLayoutSubviews. Це спрацювало, і зміна позиції більше не видно користувачеві. (Він також працював у viewWillLayoutSubviews). Тож дякую за цю пораду!
миротип

Відповіді:


185

Я активувати і деактивувати NSLayoutConstraintsв viewDidLoad, і у мене немає ніяких проблем з ним. Так воно і працює. Має бути різниця в налаштуваннях між вашим додатком та моїм :-)

Я просто опишу мою настройку - можливо, це може привести до вас:

  1. Я встановлюю @IBOutletsвсі обмеження, які мені потрібно активувати / деактивувати.
  2. У програмі ViewControllerя зберігаю обмеження у властивості класу, які не є слабкими. Причиною цього є те, що я виявив, що після дезактивації обмеження я не міг його повторно активувати - це було нульовим. Отже, воно, здається, видаляється при відключенні.
  3. Я не використовую, NSLayoutConstraint.deactivate/activateяк ти, я constraint.active = YES/ NOзамість цього.
  4. Після встановлення обмежень я дзвоню view.layoutIfNeeded().

131
"збережіть обмеження у властивостях класу, які не слабкі" Ви зекономили мені багато часу, дякую!
OpenUserX03

10
"Я зберігаю обмеження у властивостях класу, які не слабкі": Це врятувало мені тон душі. Я не знав, що викликаю селектор на об'єкт "нуль". Дякую!!
static0886

4
Важливо зазначити, що "неактивні" обмеження не ігноруються автоматичним компонуванням, вони видаляються. Активізація / деактивація обмежень фактично додає їх та видаляє. Я витратив деякий час налагодження конфліктуючого автоматичного макета після того, як я додав обмеження, які раніше я встановив, .active = falseочікуючи, що вони будуть ігноровані, поки я не встановлю їх на активні.
lbarbosa

1
збережіть обмеження у властивостях класу, які не слабкі. Спасибі людино!
MegaManX

3
Яблучний doc каже: Активізація або деактивація викликів обмеження addConstraint ( :) та RemoveConstraint ( :) на вигляд, який є найближчим спільним предком елементів, якими керує це обмеження. Використовуйте цю властивість замість виклику addConstraint ( :) або видалити безпосередньоConstraint ( :) безпосередньо. Таким чином, здається, що коли обмеження деактивовано, воно видаляється і тоді немає жодних чітких посилань на обмеження, якщо тільки IBOutlet не є сильним. Звідси обмеження видалено. ІМХО це майже помилка чи принаймні дуже несподівана поведінка.
Olle Raab

52

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

Іноді це тому, що active = NOвстановлено self.yourConstraint = nil, так що ви не можете використовувати self.yourConstraintзнову.


5
Як зазначено в посібнику з мови Swift , властивості за замовчуванням сильні, тому ви також можете просто видалити, weakі це буде робити.
Джонатан Кабрера

30
override func viewDidLayoutSubviews() {
// do it here, after constraints have been materialized
}

1
Оскільки мій контролер подання є контролем дочірнього перегляду - це робиться в "didLayoutSubviews" - це єдиний спосіб, який працював !! FYI.
TalL

Це єдина правильна відповідь
Юнус Ерен Гюзель

@TalL Ви мали на увазі обмеження щодо самого контролера дочірнього перегляду, або це підзадання?
Стефан

це найкращий
ACAkgul

14

Я вважаю, що проблема, з якою ви стикаєтеся, пов’язана з тим, що до їх поглядів не додаються обмеження, поки не 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, обмеження не "деактивує", воно все одно матиме ефект і впливатиме на макет. Може здатися, що воно «деактивує» обмеження в більшості випадків, але точно не у всіх випадках. (зокрема, не той випадок, який
змусив

Це може призвести до збоїв, як, наприклад, "Не змінюється пріоритет із вимкнення обов'язкового на встановлене обмеження (або навпаки). Ви передали пріоритет 250 і існуючий пріоритет був 1000."
Картік Рамеш

8

Належний час для деактивації невикористаних обмежень:

-(void)viewWillLayoutSubviews{
    [super viewWillLayoutSubviews];

    self.myLittleConstraint.active = NO;
}

Майте на увазі, що це viewWillLayoutSubviewsможна було б назвати кілька разів, тому важких розрахунків тут немає, гаразд?

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


2
Для мене єдиний надійний спосіб - коригування обмежень viewDidLayoutSubviews(). Коригування обмежень viewWillLayoutSubviews()у моєму випадку не працює.
петрсин

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.