Ramka widoku zmienia się pomiędzy viewWillAppear: i viewDidAppear:

85

Odkryłem dziwne zachowanie w mojej aplikacji, w której podłączony IBOutletma ramkę widoku połączonego między wywołaniami w moim kontrolerze widoku do viewWillAppear:i viewDidAppear:. Oto odpowiedni kod w mojej UIViewControllerpodklasie:

-(void)viewWillAppear:(BOOL)animated {
    NSLog(@"%@", self.scrollView);
}

-(void)viewDidAppear:(BOOL)animated {
    NSLog(@"%@", self.scrollView);
}

i wynikowy wynik dziennika:

MyApp[61880:c07] <UIScrollView: 0x1057eff0; frame = (0 0; 0 0); clipsToBounds = YES; autoresize = TM+BM; gestureRecognizers = <NSArray: 0x10580100>; layer = <CALayer: 0x1057f210>; contentOffset: {0, 0}>
MyApp[61880:c07] <UIScrollView: 0x1057eff0; frame = (0 44; 320 416); clipsToBounds = YES; autoresize = TM+BM; gestureRecognizers = <NSArray: 0x10580100>; layer = <CALayer: 0x1057f210>; contentOffset: {0, 0}>

Co wyraźnie pokazuje, że ramka zmienia się między dwoma wywołaniami. Chciałem zrobić konfigurację z widokiem w viewDidLoadmetodzie, ale jeśli zawartość nie jest dostępna do zmiany, dopóki nie pojawi się na ekranie, wydaje się to całkiem bezużyteczne. Co mogło się dziać?

Jumhyn
źródło
2
Czy używasz automatycznego układu? czy dodajesz ten widok w konstruktorze interfejsów czy programistycznie?
Andrea,
Automatyczne układanie jest włączone, a ten widok jest tworzony w IB na podstawie scenorysu.
Jumhyn
1
Nigdy nie korzystałem ze scenorysu, ale najprawdopodobniej jest poprawny. Korzystanie z ramki Autolayout w widokach jest ustawiane, gdy silnik autoukładu rozpoczyna obliczenia. Spróbuj zapytać o to samo zaraz po super - (void) viewDidLayoutSubviews mpethod kontrolera widoku.
Andrea
To z powodzeniem wyzwala moje zdarzenie we właściwym czasie, ale ta metoda jest również wywoływana za każdym razem, gdy wykonuję jakąkolwiek animację w widoku.
Jumhyn
1
viewDidLayoutSubviewsbyła właściwą drogą. Musiałem po prostu umieścić całą moją zawartość w podglądzie, aby metoda nie była ponownie wywoływana za każdym razem, gdy zmieniłem ramkę widoku głównego.
Jumhyn

Odpowiedzi:

111

Autolayoutdokonał ogromnej zmiany w sposobie projektowania i rozwijania GUI naszych widoków. Jedną z głównych różnic jest to, że autolayoutnie zmienia rozmiarów naszego widoku natychmiast, ale tylko wtedy, gdy jest uruchamiany, to znaczy w określonym czasie, ale możemy zmusić go do natychmiastowego ponownego obliczenia naszych ograniczeń lub oznaczyć je jako „wymagające” układu. Działa jak -setNeedDisplay.
Dużym wyzwaniem było dla mnie zrozumienie i zaakceptowanie tego, że nie musimy już używać masek autorezowania, a rama stała się bezużyteczną właściwością w umieszczaniu naszych widoków. Nie musimy już myśleć o położeniu widoku, ale powinniśmy pomyśleć, jak chcemy je widzieć w powiązanej ze sobą przestrzeni.
Kiedy chcemy połączyć starą maskę autorezowania i autoukład, pojawiają się problemy. Już niedługo powinniśmy pomyśleć o wdrożeniu autoukładu i starać się unikać mieszania starego podejścia w hierarchii widoków opartej na autoukładzie.
Dobrze jest mieć widok kontenera, który używa tylko masek autorealizacji, takich jak widok główny kontrolera widoku, ale jest lepszy, jeśli nie próbujemy mieszać.
Nigdy nie korzystałem ze scenorysu, ale najprawdopodobniej jest poprawny. Korzystając z funkcji Autolayout, ramki widoków są ustawiane, gdy silnik autoukładu rozpoczyna obliczenia. Spróbuj zapytać o to samo zaraz po super - (void)viewDidLayoutSubviewsmetodzie kontrolera widoku.
Ta metoda jest wywoływana, gdy mechanizm automatycznego układania zakończy obliczanie ramek widoków.

Andrea
źródło
5
- (void) viewDidLayoutSubviews to odpowiedź dla mnie! Wielkie dzięki!
FrizzTheSnail
Ta odpowiedź jest naprawdę nieprawidłowa. Tak, oczywiście, od (5?) Lat musisz korzystać z autoukładu. Ale jest wiele sytuacji ( przy użyciu automatycznego układu ), w których trzeba, powiedzmy, dodać coś na ekranie „tuż przed tym, zanim pojawi się to użytkownikowi”. (Jeśli zrobisz to w viewDidAppear, dostaniesz migotanie. Jeśli zrobisz to w viewWillAppear - pozycje będą błędne). Rzeczywista odpowiedź to rzeczywiście użycie viewDidLayoutSubviews.
Fattie
add something on a screen, "just before it appears to the user". The actual answer is indeed to use viewDidLayoutSubviews.... będziesz pokazywał ten widok wiele razy
Andrea
156

Z dokumentacji:

viewWillAppear:

Powiadamia kontroler widoku, że jego widok ma zostać dodany do hierarchii widoków.

viewDidAppear:

Powiadamia kontroler widoku, że jego widok został dodany do hierarchii widoków.

W rezultacie klatki podglądów podrzędnych nie są jeszcze ustawione w viewWillAppear :

Odpowiednią metodą modyfikacji interfejsu użytkownika przed wyświetleniem widoku na ekranie jest:

viewDidLayoutSubviews

Powiadamia kontroler widoku, że jego widok właśnie przedstawił widoki podrzędne.

gsach
źródło
2
Ugh, to jest denerwujące. Już samo sformułowanie dokumentacji sprawia, że ​​wydaje się, że viewWillApepar: i viewDidAppear: powinny następować bezpośrednio po sobie.
Jumhyn
2 miesiące czekania na tę odpowiedź ... dzięki, że znalazłem !! DZIĘKUJĘ CI BARDZO!
Rafael Ruiz Muñoz
6
Należy zauważyć, że viewDidLayoutSubviewsbędzie wywoływany wiele razy i nie zawsze z tą samą ramką (myślę, że czasami jest wywoływany z CGRectZero przy pierwszym wywołaniu). Jest wywoływana przy każdym dodanym widoku podrzędnym i innych zmianach w widoku.
bauerMusic
Wtrącałem się do tego przez cały dzień (wielokrotnie wywoływano viewDidLayoutSubviews, w tym przy odrzucaniu widoku) i właśnie powiedziałem ef z tym, wstawiłem logikę do instrukcji if i wykonałem bool, który zostaje ustawiony na true po kod został uruchomiony raz. Myślę, że musi istnieć czystszy sposób, ale już za dużo czasu.
elektromagnes
musimy dotrzeć do sedna tego! viewDidLayoutSubviews jest okropny, seNeedDisplay nie działa
Yaro
9

połączenie

self.scrollView.layoutIfNeeded ()

w swojej viewWillAppearmetodzie. Następnie możesz uzyskać dostęp do jego ramki i będzie miała taką samą wartość, jak w przypadku drukowaniaviewDidAppear

andrei
źródło
1
Nie, to nie jest poprawne. Przykład: masz pasek nawigacyjny na ekranie (z kontrolera nawigacyjnego). Nawet po layoutIfNeeded () wysokość paska nawigacyjnego nie jest uwzględniana, więc rozmiar ramki ulegnie zmianie.
xaphod
To JEST dobry sposób, aby wymusić ponowne obliczenie wymiaru ramki Scrollview tak, że jest ona zgodna całej hierarchii połączeń układu widok jeśli rama Scrollview jest związane z ograniczeniami w obrębie swojej SuperView.
smakus
4

W moim przypadku przeniesienie wszystkich metod związanych z ramką do

override func viewWillLayoutSubviews()

działało idealnie (próbowałem zmodyfikować ograniczenia z storyboardu).

Siddhesh Mahadeshwar
źródło