setNeedsLayout vs. setNeedsUpdate Ograniczenia i układIfNeeded vs updateConstraintsIfNeeded

227

Wiem, że łańcuch automatycznego układu składa się zasadniczo z 3 różnych procesów.

  1. aktualizacja ograniczeń
  2. widoki układu (tutaj otrzymujemy obliczenia ramek)
  3. pokaz

To, co nie jest do końca jasne, to wewnętrzna różnica między -setNeedsLayouti -setNeedsUpdateConstraints. Z Apple Docs:

setNeedsLayout

Wywołaj tę metodę w głównym wątku aplikacji, jeśli chcesz dostosować układ widoków podrzędnych widoku. Ta metoda odnotowuje żądanie i natychmiast zwraca. Ponieważ ta metoda nie wymusza natychmiastowej aktualizacji, ale zamiast tego czeka na następny cykl aktualizacji, można jej użyć do unieważnienia układu wielu widoków przed aktualizacją któregokolwiek z tych widoków. To zachowanie pozwala skonsolidować wszystkie aktualizacje układu w jednym cyklu aktualizacji, co zwykle jest lepsze ze względu na wydajność.

setNeedsUpdateConstraints

Gdy właściwość widoku niestandardowego zmienia się w sposób, który wpływałby na ograniczenia, można wywołać tę metodę, aby wskazać, że ograniczenia muszą zostać zaktualizowane w pewnym momencie w przyszłości. System wywoła następnie updateConstraints jako część normalnego przejścia do układu. Aktualizowanie wiązań naraz tuż przed ich użyciem zapewnia, że ​​nie trzeba niepotrzebnie ponownie obliczać wiązań, gdy między zmianami układu wprowadzanych jest wiele zmian w widoku.

Kiedy chcę animować widok po zmodyfikowaniu ograniczenia i animować zmiany, które zwykle wywołuję na przykład:

[UIView animateWithDuration:1.0f delay:0.0f usingSpringWithDamping:0.5f initialSpringVelocity:1 options:UIViewAnimationOptionCurveEaseInOut animations:^{
        [self.modifConstrView setNeedsUpdateConstraints];
        [self.modifConstrView layoutIfNeeded];
    } completion:NULL];

Odkryłem, że jeśli mogę użyć -setNeedsLayoutzamiast -setNeedsUpdateConstraintswszystko działa zgodnie z oczekiwaniami, ale jeśli zmieni -layoutIfNeededsię -updateConstraintsIfNeeded, animacja nie stanie.
Próbowałem wyciągnąć własny wniosek:

  • -updateConstraintsIfNeeded aktualizuje tylko ograniczenia, ale nie wymusza przejścia układu na proces, dlatego oryginalne ramki są nadal zachowywane
  • -setNeedsLayoutwywołuje także -updateContraintsmetodę

Więc kiedy można używać jednego zamiast drugiego? a jeśli chodzi o metody układu, czy muszę je wywoływać w widoku, który ma zmianę ograniczenia, czy w widoku nadrzędnym?

Andrea
źródło
27
Nie rozumiem ludzi, którzy oddają głos ... naprawdę. Więc powinieneś coś z tym zrobić, na przykład podać powód jako obowiązkowy lub są one całkowicie bezcelowe
Andrea
7
Być może muszą po prostu zdobyć odznakę Krytyczną (pierwsze głosowanie w dół)
fujianjin6471
1
Gorąco polecam do zobaczenia tutaj . Odpowiedź jest raczej rozwiązaniem prawdziwego problemu. Zobacz także wideo
Honey

Odpowiedzi:

258

Twoje wnioski są słuszne. Podstawowy schemat to:

  • setNeedsUpdateConstraintszapewnia przyszłe połączenie z updateConstraintsIfNeededpołączeniami updateConstraints.
  • setNeedsLayoutzapewnia przyszłe połączenie z layoutIfNeededpołączeniami layoutSubviews.

Kiedy layoutSubviewsjest wywoływany, również dzwoni updateConstraintsIfNeeded, więc moim zdaniem rzadko jest potrzebne ręczne wywoływanie go. W rzeczywistości nigdy go nie nazwałem, z wyjątkiem debugowania układów.

Aktualizowanie za pomocą ograniczeń setNeedsUpdateConstraintsjest również dość rzadkie, objc.io - należy przeczytać o autoukładach - mówi :

Jeśli później coś się zmieni, co unieważnia jedno z twoich ograniczeń, powinieneś natychmiast je usunąć i wywołać setNeedsUpdateConstraints. W rzeczywistości jest to jedyny przypadek, w którym powinieneś uruchomić wyzwalanie aktualizacji ograniczenia.

Ponadto, z mojego doświadczenia, nigdy nie musiałem unieważniać ograniczeń i nie ustawiać setNeedsLayoutnastępnego wiersza kodu, ponieważ nowe ograniczenia w zasadzie wymagają nowego układu.

Podstawowe zasady to:

  • Jeśli bezpośrednio manipulowałeś ograniczeniami, zadzwoń setNeedsLayout.
  • Jeśli zmieniono pewne warunki (jak offsetu lub czymś), która będzie zmieniać ograniczeń w swojej zastąpionej updateConstraintsmetoda (zalecanym sposobem ograniczenia zmian, btw), zadzwoń setNeedsUpdateConstraints, a większość czasu, setNeedsLayoutpo tym.
  • Jeśli potrzebujesz któregoś z powyższych działań, aby uzyskać natychmiastowy efekt - np. Gdy musisz nauczyć się nowej wysokości ramki po przejściu układu - dodaj ją za pomocą layoutIfNeeded.

Ponadto w twoim kodzie animacji uważam, że nie setNeedsUpdateConstraintsjest to konieczne, ponieważ ograniczenia są aktualizowane ręcznie przed animacją, a animacja rozkłada widok tylko na podstawie różnic między starymi i nowymi.

okładka
źródło
@coverback, więc objc.io mówi: „Jeśli później coś się zmieni, co unieważnia jedno z twoich ograniczeń, powinieneś natychmiast usunąć ograniczenie i wywołać setNeedsUpdateConstraints. W rzeczywistości jest to jedyny przypadek, w którym powinieneś uruchomić wyzwalanie aktualizacji ograniczenia”. A potem w bloku Animacja mówi, że kiedy usuwam, dodam lub zmieniam constraint.contant muszę wywołać setNeedsLayout. Co za różnica? Czuję się naprawdę głupio :(
pash3r
3
@ pash3r Różnica w stałej aktualizacji nie kwalifikuje się jako „unieważnienie”. Unieważnienie ma miejsce wtedy, gdy przestaje być w ogóle istotne, np. Musi zostać dołączone do innego widoku lub całkowicie usunięte. Stała po prostu umieszczałaby widok bliżej lub dalej, lub zmieniałaby jego rozmiar, a zatem potrzeba setNeedsLayout.
coverback
@ okładka gwarantuje, setNeedsLayoutże layoutSubviewszostanie wywołana w następnym cyklu aktualizacji, ale może to nie ma nic wspólnego layoutIfNeeded?
fujianjin6471
2
@ okładka Jeśli bezpośrednio manipulujesz ograniczeniami, layoutSubviewszostanie wywołany automatycznie, nie musisz dzwonićsetNeedsLayout
fujianjin6471
Tak, bezpośrednie manipulowanie właściwościami ograniczenia zostanie uruchomione layoutSubviews, więc nie trzeba tego robić ręcznie. Musisz jednak zadzwonić, layoutIfNeededjeśli chcesz, aby zmiany odniosły skutek natychmiast zamiast następnego cyklu układu
Charlie Martin
89

Odpowiedź przez coverback jest całkiem poprawne. Chciałbym jednak dodać kilka dodatkowych szczegółów.

Poniżej znajduje się schemat typowego cyklu UIView, który wyjaśnia inne zachowania:

Cykl życia UIView

  1. Odkryłem, że jeśli mogę użyć -setNeedsLayoutzamiast -setNeedsUpdateConstraintswszystko działa zgodnie z oczekiwaniami, ale jeśli zmieni -layoutIfNeededsię -updateConstraintsIfNeeded, animacja nie stanie.

updateConstraintszazwyczaj nic nie robi. Po prostu rozwiązuje ograniczenia, ale nie stosuje ich, dopóki nie layoutSubviewszostanie wywołane. Tak więc animacja wymaga połączenia z layoutSubviews.

  1. setNeedsLayout wywołuje również metodę -updateContraints

Nie, to nie jest konieczne. Jeśli twoje ograniczenia nie zostały zmodyfikowane, UIView pominie połączenie z updateConstraints. Musisz jawnie zadzwonić, setNeedsUpdateConstraintaby zmodyfikować ograniczenia w tym procesie.

Aby zadzwonić updateConstraints, wykonaj następujące czynności:

[view setNeedsUpdateConstraints];
[view setNeedsLayout]; 
[view layoutIfNeeded];
Kunal Balani
źródło
Dzięki, to rozwiązało mój problem. Miałem UIWindow bez nadrzędnego UIView, który miał tymczasowe ograniczenia dodawane do niego po wywołaniu LayoutIfNeeded () przed animacją. Dodanie opakowania subview do UIWindow i wywołanie na nim tych trzech metod naprawiło mój problem.
masterwok
Nie sądzę, aby wywołanie layoutIfNeeded zaraz po ustawieniu setNeedsLayout było poprawne. Ponieważ metody robią to samo, mimo że jedna powoduje natychmiastowe przerysowanie układu, a druga w następnym cyklu aktualizacji.
fillky