Kiedy nazywa się layoutSubviews?

264

Mam widok niestandardowy, który nie layoutSubviewwyświetla wiadomości podczas animacji.

Mam widok, który wypełnia ekran. Ma niestandardowy widok podrzędny u dołu ekranu, który zmienia rozmiar w Konstruktorze interfejsów, jeśli zmienię wysokość paska nawigacyjnego. layoutSubviewsjest wywoływany podczas tworzenia widoku, ale nigdy więcej. Moje podstrony są poprawnie ułożone. Jeśli wyłączę pasek stanu podczas połączenia, widok podrzędny layoutSubviewsnie zostanie w ogóle wywołany, nawet jeśli główny widok animuje jego zmianę rozmiaru.

W jakich okolicznościach layoutSubviewsfaktycznie się nazywa?

Mam autoresizesSubviewsustawione NOdla mojego widoku niestandardowego. W Konstruktorze interfejsów mam górne i dolne rozpórki oraz zestaw strzałek pionowych.


Inną częścią układanki jest to, że okno musi być wykonane jako klucz:

[window makeKeyAndVisible];

w przeciwnym razie widoki podrzędne nie są automatycznie zmieniane.

Steve Weller
źródło

Odpowiedzi:

492

Miałem podobne pytanie, ale nie byłem usatysfakcjonowany odpowiedzią (lub jakąkolwiek, którą mogłem znaleźć w sieci), więc wypróbowałem ją w praktyce i oto, co otrzymałem:

  • initnie powoduje, layoutSubviewsże się nazywa (duh)
  • addSubview:powoduje layoutSubviewswywołanie dodawanego widoku, widoku, do którego jest dodawany (widok docelowy) oraz wszystkich widoków podrzędnych celu
  • view setFrame inteligentnie wywołuje layoutSubviewswidok z ustawioną ramką tylko wtedy, gdy parametr rozmiaru ramki jest inny
  • przewijanie UIScrollView powoduje layoutSubviewswywołanie w scrollView i jego superwizji
  • obracanie urządzenia wywołuje tylko layoutSubviewwidok nadrzędny (widok podstawowy odpowiadającego widoku Kontrolery)
  • Zmiana rozmiaru widoku wymaga layoutSubviewsjego podglądu

Moje wyniki - http://blog.logichigh.com/2011/03/16/when-does-layoutsubviews-get-called/

BadPirate
źródło
1
Świetna odpowiedź. Zawsze się nad tym zastanawiałem layoutSubviews. Czy initWithFrame:wywoływana layoutSubviewsjest przyczyna ?
Robert
2
@Robert - używałem initWithFrame ... więc nie.
BadPirate,
8
@BadPirate: tak . Według moich doświadczeń, jeśli rozmiar view1.1wywołuje layoutSubviewsof view1a następnie layoutSubviewsod view1.1. To wywołanie nie rozprzestrzenia się w nieskończoność na superwizje, wywołując je view1.1.1tylko layoutSubviewsna wywołaniach view1.1i view1.1.1. Samo poruszanie się bez zmiany jego rozmiaru nie wywołuje layoutSubviewsżadnego z nich.
João Portela
1
viewDidLoad nie jest wywoływany w UIView (ale UIViewController). Wyświetl Czy ładowanie jest wywoływane po zainicjowaniu UIView.
BadPirate
2
Na podstawie mojego doświadczenia, druga zasada może nie być dokładne: Kiedy dodać view1.2do view1, layoutSubviewsz view1.2i view1nazywane są, ale layoutSubviewsz view1.1nie nazywa. ( view1.1i view1.2są podstronami view1). Oznacza to, że nie wszystkie widoki cząstkowe widoku docelowego nazywane są layoutSubviewsmetodą .
HongchaoZhang
96

Opierając się na poprzedniej odpowiedzi @BadPirate, eksperymentowałem trochę dalej i wymyśliłem kilka wyjaśnień / poprawek. Stwierdziłem, że layoutSubviews:zostanie wywołany w widoku tylko wtedy, gdy:

  • Zmieniono własne granice (nie ramkę).
  • Granice jednego z jego bezpośrednich widoków podrzędnych uległy zmianie.
  • Widok podrzędny jest dodawany do widoku lub usuwany z widoku.

Niektóre istotne szczegóły:

  • Granice są uważane za zmienione tylko wtedy, gdy nowa wartość jest inna, w tym inne pochodzenie . Zwróć uwagę, że właśnie dlatego layoutSubviews:jest wywoływany za każdym razem, gdy przewija się UIScrollView, ponieważ wykonuje przewijanie, zmieniając początek jego granic.
  • Zmiana ramki spowoduje zmianę granic tylko wtedy, gdy zmieni się rozmiar, ponieważ jest to jedyna rzecz propagowana do właściwości bounds.
  • Zmiana granic widoku, które nie jest jeszcze w hierarchii widoku, spowoduje wywołanie, layoutSubviews: gdy widok zostanie ostatecznie dodany do hierarchii widoku .
  • I tylko dla kompletności: te wyzwalacze nie wywołują bezpośrednio podglądu układu, ale raczej wywołują setNeedsLayout, która ustawia / podnosi flagę. Każda iteracja pętli uruchamiania, dla wszystkich widoków w hierarchii widoków , ta flaga jest sprawdzana. Dla każdego widoku, w którym znaleziono flagę, layoutSubviews:wywoływana jest na nią i flaga jest resetowana. Widoki wyżej w hierarchii będą najpierw sprawdzane / wywoływane.
Patrick Pijnappel
źródło
5
nie mogę głosować wystarczająco za odpowiedzią. powinna być najlepsza odpowiedź. trzy podane zasady są wszystkim, czego potrzebujesz. nigdy nie spotkałem żadnego zachowania dotyczącego widoku, którego reguły nie opisały idealnie.
Pärserk
1
Nie sądzę, że layoutSubviews jest wywoływany, gdy zmieniane są granice bezpośredniego widoku podrzędnego. Myślę, że wywołanie layoutSubviews jest tylko efektem ubocznym twoich testów. W niektórych przypadkach layoutSubviews nie zostanie wywołany, gdy zmienią się granice widoków podrzędnych. Sprawdź odpowiedź frogcjna, ponieważ jego odpowiedź oparta jest na dokumentacji Apple zamiast na eksperymentach.
Simon Backx,
19

https://developer.apple.com/library/prerelease/tvos/documentation/WindowsViews/Conceptual/ViewPG_iPhoneOS/CreatingViews/CreatingViews.html#//apple_ref/doc/uid/TP40009503-CH5-SW1

Zmiany układu mogą wystąpić, gdy w widoku wystąpi dowolne z następujących zdarzeń:

za. Rozmiar prostokąta obwiedni widoku zmienia się.
b. Następuje zmiana orientacji interfejsu, która zwykle powoduje zmianę prostokąta granic widoku głównego.
do. Zestaw podwarstwowych animacji powiązanych z warstwami widoku zmienia się i wymaga układu.
re. Aplikacja wymusza układ, wywołując   metodę setNeedsLayout lub  layoutIfNeededmetodę widoku.
mi. Aplikacja wymusza układ, wywołując  setNeedsLayout metodę bazowego obiektu warstwy widoku.

frogcjn
źródło
Należy również zauważyć, że żadne z tych zdarzeń nie jest wywoływane, gdy widok nie został jeszcze dodany do stosu widoków. Dołączasz to słowem „może”, ale jest ono wywoływane w następnym dostępnym cyklu głównego wątku aplikacji, gdy zostanie oznaczone jako wymagające układu przez jedno z tych zdarzeń.
user1122069,
13

Niektóre punkty w odpowiedzi BadPirate są tylko częściowo prawdziwe:

  1. Za addSubViewpunkt

    addSubview powoduje, że layoutSubviews jest wywoływany przy dodawanym widoku, widoku, do którego jest dodawany (widok docelowy), i wszystkich widokach podrzędnych celu.

    Zależy to od maski automatycznej zmiany widoku (widok docelowy). Jeśli ma WŁĄCZONĄ maskę automatyczną, dla każdego wywoływany jest layoutSubview addSubview. Jeśli nie ma maski automatycznej zmiany rozmiaru, layoutSubview zostanie wywołany tylko wtedy, gdy zmieni się rozmiar ramki widoku (Widok docelowy).

    Przykład: jeśli programowo utworzono UIView (domyślnie nie ma maski autoresize), LayoutSubview zostanie wywołany tylko wtedy, gdy ramka UIView nie zmieni się co raz addSubview.

    Dzięki tej technice zwiększa się również wydajność aplikacji.

  2. Dla punktu obrotu urządzenia

    Obracanie urządzenia wywołuje tylko układ Widok w widoku rodzica (widok główny odpowiadającego widoku Widok główny kontrolera)

    Może to być prawdą tylko wtedy, gdy twój VC znajduje się w hierarchii VC (root at window.rootViewController), więc jest to najczęstszy przypadek. W iOS 5, jeśli utworzysz VC, ale nie zostanie on dodany do żadnego innego VC, to ten VC nie zostanie zauważony, gdy urządzenie się obróci. Dlatego jego widok nie zostałby zauważony przez wywołanie layoutSubviews.

Mohit Nigam
źródło
9

Prześledziłem rozwiązanie aż do nalegania Interface Buildera, że ​​sprężyn nie można zmienić w widoku, w którym włączono symulowane elementy ekranu (pasek stanu itp.). Ponieważ sprężyny były wyłączone dla widoku głównego, widok ten nie mógł zmienić rozmiaru i dlatego został przewinięty w dół, gdy pojawił się pasek wywołania.

Wyłączenie symulowanych funkcji, a następnie zmiana rozmiaru widoku i prawidłowe ustawienie sprężyn spowodowały uruchomienie animacji i wywołanie mojej metody.

Dodatkowym problemem podczas debugowania jest to, że symulator zamyka aplikację, gdy status połączenia jest zmieniany za pomocą menu. Wyjdź z aplikacji = brak debugera.

Steve Weller
źródło
Czy mówisz, że layoutSubviews jest wywoływany podczas zmiany rozmiaru widoku? Zawsze zakładałem, że to nie jest ...
Andriej Tarantow
To jest. Po zmianie rozmiaru widoku należy zrobić coś z jego widokami podrzędnymi. Jeśli go nie podasz, przesuwa je automatycznie za pomocą sprężyn, rozpórek itp.
Steve Weller
8

wywołanie [self.view setNeedsLayout]; w viewController umożliwia wywołanie viewDidLayoutSubviews

bademi
źródło
5

czy spojrzałeś na layoutIfNeeded?

Fragment dokumentacji znajduje się poniżej. Czy animacja działa, jeśli wywołasz tę metodę jawnie podczas animacji?

layoutIfNeeded W razie potrzeby określa widoki podrzędne.

- (void)layoutIfNeeded

Dyskusja Użyj tej metody, aby wymusić układ widoków podrzędnych przed rysowaniem.

Dostępność Dostępne w iPhone OS 2.0 i nowszych.

Willi Ballenthin
źródło
2

Podczas migracji aplikacji OpenGL z SDK 3 do 4, layoutSubviews nie był już nazywany. Po wielu próbach i błędach w końcu otworzyłem MainWindow.xib, wybrałem obiekt Window, w inspektorze wybrałem zakładkę Window Attributes (skrajnie lewy) i zaznaczyłem „Visible at launch”. Wygląda na to, że w SDK 3 nadal wywoływał wywołanie layoutSubViews, ale nie w 4.

Zakończono 6 godzin frustracji.

Tal Yaniv
źródło
Zrobiłeś klucz do okna? Jeśli nie, może to spowodować, że nie wydarzy się wiele interesujących rzeczy.
Steve Weller,
-2

Dość niejasnym, ale potencjalnie ważnym przypadkiem, gdy layoutSubviewsnigdy nie zostanie wywołany, jest:

import UIKit

class View: UIView {

    override class var layerClass: AnyClass { return Layer.self }

    class Layer: CALayer {
        override func layoutSublayers() {
            // if we don't call super.layoutSublayers()...
            print(type(of: self), #function)
        }
    }

    override func layoutSubviews() {
        // ... this method never gets called by the OS!
        print(type(of: self), #function)
    }
}

let view = View(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
milos
źródło
1
Dlaczego ta odpowiedź jest tutaj?
Dominik Bucher