Kiedy mogę aktywować / dezaktywować ograniczenia układu?

104

Skonfigurowałem wiele zestawów ograniczeń w IB i chciałbym programowo przełączać się między nimi w zależności od stanu. Istnieje constraintsAkolekcja outlet, z których wszystkie są oznaczone jako zainstalowane z IB oraz constraintsBkolekcja outlet, z których wszystkie są odinstalowane w IB.

Mogę programowo przełączać się między tymi dwoma zestawami w następujący sposób:

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

Ale ... Nie wiem, kiedy to zrobić. Wygląda na to, że kiedyś powinienem to zrobić viewDidLoad, ale nie mogę tego zmusić. Próbowałem dzwonić view.updateConstraints()i view.layoutSubviews()po ustawieniu ograniczeń, ale bezskutecznie.

Zauważyłem, że jeśli ustawię ograniczenia, viewDidLayoutSubviewswszystko działa zgodnie z oczekiwaniami. Chyba chciałbym wiedzieć dwie rzeczy ...

  1. Dlaczego otrzymuję takie zachowanie?
  2. Czy można aktywować / dezaktywować ograniczenia z viewDidLoad?
tybro0103
źródło
2
Czy masz na myśli, że dezivateConstraints i activConstraints działały w viewWillLayoutSubviews? Próbowałem tego i nie działało tam ani w widokuDidLoad. To działało w ramach viewDidAppear; widok pojawił się tam, gdzie powinny go umieścić nowe ograniczenia, ale jeśli obróciłem go do pozycji poziomej, widok powrócił do pozycji określonej przez ograniczenia ustawione w IB (i pozostał tam, gdy obróciłem z powrotem do portretu). Logując ograniczenia, pokazałem poprawne (te nowo aktywowane). Wydaje mi się, że to błąd.
rdelmar
1
Tak, były prawidłowe (działały w viewDidAppear) i nie ma potrzeby wywoływać super, ponieważ nie ma domyślnej implementacji viewWillLayoutSubviews (i tak próbowałem to z wywołaniem super, ale to nie miało znaczenia).
rdelmar
1
@rdelmar Właśnie miałem okazję przetestować więcej ... Mogę zweryfikować, że faktycznie mam to samo zachowanie, które opisałeś ... działa na początku w viewDidAppear, ale potem powraca po obróceniu.
tybro0103
3
Najwyraźniej nie możesz w tym celu oznaczać ograniczeń jako niezainstalowanych w IB. Znalazłem te informacje tutaj: stackoverflow.com/questions/27663249/ ... i to rozwiązało problem za mnie.
Stefan
1
Moje ograniczenia zostały zaimplementowane w taki sam sposób, jak opisano w pytaniu, z wyjątkiem tego, że niektóre z nich aktywowałem / dezaktywowałem w viewDidAppear. To zadziałało, ale można było zobaczyć, jak elementy szybko zmieniają pozycję (drobny, ale niepożądany problem). Wprowadzanie zmiany w viewWillAppear lub viewDidLoad nie działa. Ale po przeczytaniu tego pytania spróbowałem wprowadzić tę zmianę w viewDidLayoutSubviews. Zadziałało i zmiana pozycji nie jest już widoczna dla użytkownika. (Działał również w viewWillLayoutSubviews). Więc dzięki za tę wskazówkę!
pokój z

Odpowiedzi:

186

Włączam i dezaktywuję NSLayoutConstraintsw viewDidLoadi nie mam z tym żadnych problemów. Więc to działa. Musi istnieć różnica w konfiguracji między Twoją aplikacją a moją :-)

Opiszę tylko moją konfigurację - może da ci wskazówkę:

  1. Skonfigurowałem @IBOutletswszystkie ograniczenia, które muszę aktywować / dezaktywować.
  2. W programie ViewControllerzapisuję ograniczenia we właściwościach klas, które nie są słabe. Powodem tego jest to, że stwierdziłem, że po dezaktywacji ograniczenia nie mogłem go ponownie aktywować - było zero. Tak więc wydaje się, że jest usuwany po dezaktywacji.
  3. Nie używam NSLayoutConstraint.deactivate/activatetak jak ty, zamiast tego używam constraint.active = YES/ NO.
  4. Po ustawieniu ograniczeń dzwonię view.layoutIfNeeded().
Joachim Bøggild
źródło
132
„zapisz ograniczenia we właściwościach klas, które nie są słabe” Zaoszczędziłeś mi dużo czasu, dzięki!
OpenUserX03
10
„Zapisuję ograniczenia w klasowych właściwościach, które nie są słabe”: zaoszczędziło mi to mnóstwo bólu serca. Nie wiedziałem, że wywołuję selektor na zerowym obiekcie. Dzięki!!
static0886
4
Należy pamiętać, że „nieaktywne” ograniczenia nie są ignorowane przez układ automatyczny, są one usuwane. Aktywacja / dezaktywacja ograniczeń faktycznie je dodaje i usuwa. Poświęciłem trochę czasu na debugowanie konfliktu automatycznego układu po dodaniu ograniczeń, które ustawiłem wcześniej, .active = falsespodziewając się, że zostaną zignorowane, dopóki nie ustawię ich jako aktywnych.
lbarbosa
1
zapisz ograniczenia we właściwościach klas, które nie są słabe, ok, to oszczędza dużo czasu, bez tego otrzymywałem mieszane wyniki. Dzięki!
MegaManX,
3
Dokument Apples mówi: Aktywacja lub dezaktywacja ograniczenia wywołuje addConstraint ( :) i removeConstraint ( :) w widoku, który jest najbliższym wspólnym przodkiem elementów zarządzanych przez to ograniczenie. Użyj tej właściwości zamiast bezpośrednio wywoływać addConstraint ( :) lub removeConstraint ( :). Wydaje się więc, że gdy ograniczenie jest dezaktywowane, jest usuwane, a następnie nie ma silnych odniesień do ograniczenia, chyba że IBOutlet jest silny. W związku z tym ograniczenie zostało usunięte. IMHO to prawie błąd lub przynajmniej bardzo nieoczekiwane zachowanie.
Olle Raab
52

Być może można sprawdzić swoje @propertieswymienić weakzstrong .

Czasami to dlatego , że active = NOustawione self.yourConstraint = nil, aby nie można było użyć self.yourConstraintponownie.

Lew
źródło
5
Jak stwierdzono w przewodniku Swift Language Guide , właściwości są domyślnie mocne, więc możesz je po prostu usunąć weaki to wystarczy.
Jonathan Cabrera
30
override func viewDidLayoutSubviews() {
// do it here, after constraints have been materialized
}
Tony Swiftguy
źródło
1
Ponieważ mój kontroler widoku jest podrzędnym kontrolerem widoku - zrobienie tego w „didLayoutSubviews” wydaje się być jedynym sposobem, który zadziałał !! FYI.
TalL
To jedyna poprawna odpowiedź
Yunus Eren Güzel
@TalL Czy chodziło Ci o ograniczenia samego kontrolera widoku podrzędnego, czy też podglądy?
Stefan,
ten jest najlepszy
ACAkgul
14

Uważam, że problem, którego doświadczasz, jest spowodowany tym, że ograniczenia nie są dodawane do ich widoków, dopóki nie viewDidLoad()zostanie wywołana funkcja AFTER . Masz kilka opcji:

A) Możesz połączyć swoje ograniczenia układu z IBOutlet i uzyskać do nich dostęp w swoim kodzie za pomocą tych odwołań. Ponieważ gniazda są podłączone przed viewDidLoad()uruchomieniem, ograniczenia powinny być dostępne i można je tam nadal aktywować i dezaktywować.

B) Jeśli chcesz użyć constraints()funkcji UIView, aby uzyskać dostęp do różnych ograniczeń, musisz poczekać na viewDidLayoutSubviews()rozpoczęcie i zrobić to tam, ponieważ jest to pierwszy punkt po utworzeniu kontrolera widoku z końcówki, że będzie on miał zainstalowane ograniczenia. Nie zapomnij zadzwonić, layoutIfNeeded()kiedy skończysz. Ma to tę wadę, że przebieg układu zostanie wykonany dwukrotnie, jeśli są jakieś zmiany do zastosowania i należy upewnić się, że nie ma możliwości wywołania nieskończonej pętli.

Krótkie ostrzeżenie: wyłączone ograniczenia NIE są zwracane przez constraints() metodę! Oznacza to, że jeśli NIE wyłączysz ograniczenia z zamiarem ponownego włączenia go później, będziesz musiał zachować do niego odniesienie.

C) Możesz zapomnieć o podejściu do scenorysu i zamiast tego ręcznie dodać ograniczenia. Ponieważ robisz to w trakcie viewDidLoad(), zakładam, że intencją jest zrobienie tego tylko raz przez cały okres użytkowania obiektu, a nie zmiana układu w locie, więc powinna to być akceptowalna metoda.

Popiół
źródło
10

Możesz również dostosować prioritywłaściwość, aby je „włączyć” i „wyłączyć” (na przykład 750 wartość do włączenia i 250 do wyłączenia). Z jakiegoś powodu zmiana activeBOOL nie miała żadnego wpływu na mój interfejs użytkownika. Nie ma potrzeby layoutIfNeededi można to ustawić i zmienić w viewDidLoad lub w dowolnym momencie po tym.

user2387149
źródło
Bardzo dobra sugestia. Zmiana priorytetu ograniczeń działa w programie viewWillTransition(to:, with:)lub viewWillLayoutSubviews()i możesz zachować wszystkie alternatywne ograniczenia jako „zainstalowane” w scenorysie. Priorytet ograniczenia nie może zmienić się z niewymaganego na wymagany, dlatego użyj poniższych wartości 1000. Z drugiej strony, aktywowanie (dodawanie) i dezaktywowanie (usuwanie) ograniczeń działa tylko viewDidLayoutSubviews()i wymaga zachowania strong @IBOutletodniesień do NSLayoutConstraint-s.
Gary,
„Z jakiegoś powodu zmiana aktywnego BOOL nie miała żadnego wpływu na mój interfejs użytkownika”. Na podstawie tutaj . Myślę, że nie można zmienić ograniczenia z priorytetem 1000 podczas działania. Jeśli chcesz go dezaktywować, powinieneś ustawić początkowy priorytet na 999 lub niższy ....
Kochanie
Nie zgadzam się z tym stwierdzeniem, ponieważ może to prowadzić do trudnych do debugowania problemów i nie odpowiada na pytanie. Ustawienie priorytetu na 250 nie „dezaktywuje” ograniczenia, nadal będzie miało wpływ i wpłynie na układ. Może się wydawać, że w większości przypadków „dezaktywuje” to ograniczenie, ale zdecydowanie nie we wszystkich przypadkach. (w szczególności nie przypadek, który doprowadził mnie do znalezienia odpowiedzi na to pytanie)
Tumata
Może to prowadzić do awarii, a także „Zmiana priorytetu z wymaganego na nie w zainstalowanym ograniczeniu (lub odwrotnie) nie jest obsługiwana. Przekroczyłeś priorytet 250, a istniejący priorytet to 1000”.
Karthick Ramesh
8

Właściwy czas na dezaktywację nieużywanych ograniczeń:

-(void)viewWillLayoutSubviews{
    [super viewWillLayoutSubviews];

    self.myLittleConstraint.active = NO;
}

Pamiętaj, że viewWillLayoutSubviewsmożna to wywołać wiele razy, więc nie ma tu ciężkich obliczeń, ok?

Uwaga: jeśli chcesz później reaktywować niektóre z ograniczeń, zawsze przechowuj strongdo nich odniesienie.

Laszlo
źródło
2
Dla mnie jedynym niezawodnym sposobem jest dostosowanie ograniczeń w viewDidLayoutSubviews(). Dostosowywanie ograniczeń viewWillLayoutSubviews()w moim przypadku nie działa.
petrsyn
6

Podczas tworzenia widoku wywoływane są w kolejności następujące metody cyklu życia:

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

A teraz do twoich pytań.

  1. Dlaczego otrzymuję takie zachowanie?

Odpowiedź: Ponieważ kiedy próbujesz ustawić ograniczenia dla widoków w viewDidLoadwidoku, nie ma swoich granic, dlatego nie można ich ustawić. Dopiero potem viewDidLayoutSubviewsgranice widoku są sfinalizowane.

  1. Czy można aktywować / dezaktywować ograniczenia z viewDidLoad?

Odpowiedź: Nie. Powód wyjaśniony powyżej.

Sumeet
źródło
W swoim opisie cyklu życia viewController wspomniałeś o tym, jak widok jest najpierw ładowany, a następnie wywoływana jest metoda viewDidLoad. Jednak powiedziałeś również, że widok nie jest tworzony do czasu wywołania viewDidLoad, jest to oczywiście sprzeczność. Możesz również samodzielnie przetestować i zobaczyć, że widok został utworzony do czasu wywołania metody viewDidLoad, ponieważ możesz dodać podglądy do widoku.
ABakerSmith
viewDidLoad powinno działać dobrze, ponieważ widoki są tworzone i ładowane ... W rzeczywistości, gdy aktywujesz ograniczenia, sprowadza się to głównie do wydajności. Domyślam się, że pierwotny problem nie był związany z miejscem aktywacji ograniczeń. stackoverflow.com/questions/19387998/…
Gabe
@ABakerSmith Zmieniłem odpowiedź, aby była bardziej przejrzysta.
Sumeet
1

Odkryłem, że tak długo, jak ustawiasz ograniczenia na normalne w nadpisywaniu - (void)updateConstraints(cel c), z strongodniesieniem do początkowości użytych ograniczeń aktywnych i nieaktywnych. W innym miejscu cyklu widoku dezaktywuj i / lub aktywuj to, czego potrzebujesz, a następnie dzwoniąc layoutIfNeeded, nie powinieneś mieć problemów.

Najważniejsze jest, aby nie używać ciągle nadpisywania updateConstraintsi oddzielać aktywacji ograniczeń, o ile wywołujesz updateConstraints po pierwszej inicjalizacji i układzie. Wydaje się, że ma to znaczenie po tym, gdzie w cyklu widoku.

elliotrock
źródło