Jak uzyskać bieżącą szerokość i wysokość widoku, używając ograniczeń autoukładu?

108

Nie mówię o właściwości frame, ponieważ z tego można uzyskać tylko rozmiar widoku w xib. Mówię o tym, kiedy rozmiar widoku jest zmieniany z powodu jego ograniczeń (może po obróceniu lub w odpowiedzi na zdarzenie). Czy jest sposób, aby uzyskać jego aktualną szerokość i wysokość?

Próbowałem iterować przez jego ograniczenia, szukając ograniczeń szerokości i wysokości, ale nie jest to zbyt jasne i kończy się niepowodzeniem, gdy istnieją wewnętrzne ograniczenia (ponieważ nie mogę rozróżnić między nimi). Działa to tylko wtedy, gdy faktycznie mają ograniczenia dotyczące szerokości i wysokości, których nie mają, jeśli polegają na innych ograniczeniach w celu zmiany rozmiaru.

Dlaczego jest to dla mnie takie trudne. ARG!

yuf
źródło
Wydawało mi się, że granice widoku w jakimkolwiek konkretnym momencie utrzymywały swoją obecną szerokość i wysokość. To jest dostosowywane podczas procesu tworzenia układu.
Phillip Mills,
Hmm, nigdy nie myślałem, żeby sprawdzić granice. Zrobię to teraz.
yuf

Odpowiedzi:

246

Odpowiedź jest [view layoutIfNeeded].

Dlatego:

Wciąż uzyskujesz aktualną szerokość i wysokość widoku, sprawdzając view.bounds.size.widthi view.bounds.size.height(lub ramkę, która jest równoważna, chyba że grasz z view.transform).

Jeśli chcesz mieć szerokość i wysokość sugerowaną przez istniejące ograniczenia, odpowiedzią nie jest ręczne sprawdzanie ograniczeń, ponieważ wymagałoby to ponownego zaimplementowania całej logiki rozwiązywania ograniczeń systemu automatycznego układu w celu ich zinterpretowania. ograniczenia. Zamiast tego powinieneś po prostu poprosić auto layout o zaktualizowanie tego układu , tak aby rozwiązał on ograniczenia i zaktualizował wartość view.bounds z poprawnym rozwiązaniem, a następnie sprawdzisz view.bounds.

Jak poprosić automatyczny układ o aktualizację układu? Zadzwoń, [view setNeedsLayout]jeśli chcesz, aby automatyczny układ zaktualizował układ na następnym zakręcie pętli uruchamiania.

Jednakże, jeśli chcesz, aby natychmiast zaktualizować układ, dzięki czemu można natychmiast uzyskać dostęp do nowych granic cenią później w ciągu bieżącej funkcji, lub w innym miejscu przed przełomie pętli biegu, to trzeba zadzwonić [view setNeedsLayout]i [view layoutIfNeeded].

Zadałeś drugie pytanie: „Jak mogę zmienić ograniczenie wysokości / szerokości, jeśli nie mam do niego bezpośredniego odniesienia?”.

Jeśli tworzysz ograniczenie w IB, najlepszym rozwiązaniem jest utworzenie IBOutlet w kontrolerze widoku lub widoku, aby mieć do niego bezpośrednie odniesienie. Jeśli utworzyłeś ograniczenie w kodzie, powinieneś trzymać odwołanie w wewnętrznej słabej właściwości w czasie, gdy je tworzyłeś. Jeśli ktoś inny utworzył ograniczenie, musisz je znaleźć, sprawdzając właściwość review.constraints w widoku i prawdopodobnie całą hierarchię widoków oraz implementując logikę, która znajduje kluczowe NSLayoutConstraint. Jest to prawdopodobnie niewłaściwa droga, ponieważ wymaga również skutecznego określenia, które konkretne ograniczenie określa rozmiar granic, podczas gdy nie ma gwarancji, że będzie prosta odpowiedź na to pytanie. Ostateczna wartość ograniczeń może być rozwiązaniem bardzo skomplikowanego systemu wielu ograniczeń,

glony
źródło
16
Doskonała odpowiedź i dobrze wyjaśnione, too. Uratował mnie po godzinie lub dwóch wyrywania włosów.
Ben Kreeger,
2
layoutIfNeededjest świetny i sprawi, że rama będzie natychmiast dostępna. Jednak wymusi również renderowanie ograniczeń na wszystkich widokach w poddrzewach widoku, na którym jest wywoływana. Jeśli na przykład dodajesz widoki programowo i wywołujesz layoutIfNeededje wszystkie w swojej procedurze rekurencyjnej, może się okazać, że hierarchia widoków renderuje się bardzo wolno. (Nauczyłem się tego na własnej skórze) Jak wspomniano w doskonałej odpowiedzi, „setNeedsLayout” jest bardziej wydajne i udostępni ramkę przy następnym przejściu układu, co idealnie dzieje się w około 1/60 sekundy.
shmim
1
Wiem, że to jest stare, ale gdzie nazywasz tę metodę? W initWithCoder?
MayNotBe
4
Po co wymuszać układ tylko po to, aby uzyskać granice? Wydaje się to nieefektywne, a przy złożonym interfejsie może być potencjalnie kosztowne. Zamiast tego uzyskaj dostęp do najnowszych granic z viewDidLayoutSubviews lub nawet viewWillLayoutSubviews i pozwól kakao obsługiwać własne czasy układu. Ustaw właściwość, jeśli chcesz uzyskać dostęp do wartości lub flagi, aby uniknąć wielu wywołań, jeśli stanowi to problem.
smileBot
1
Tak, musisz wymusić układ tylko wtedy, gdy potrzebujesz dostępu do wartości obliczonej automatycznie przez układ przed włączeniem pętli uruchamiania - na przykład w zakresie bieżącego wywołania funkcji.
glony
8

Miałem podobny problem, w którym musiałem dodać górną i dolną granicę do pliku UITableView który zmienia rozmiar na podstawie ustawień ograniczeń w UIStoryboard. Udało mi się uzyskać dostęp do zaktualizowanych ograniczeń za pomocą - (void)viewDidLayoutSubviews. Jest to przydatne, ponieważ nie ma potrzeby tworzenia podklasy widoku i nadpisywania jego metody układu.

/*** SET TOP AND BOTTOM BORDERS ON TABLE VIEW ***/
- (void)addBorders
{
    CALayer *topBorder           = [CALayer layer];
    topBorder.frame              = CGRectMake(0.0f, self.tableView.frame.origin.y, 320.0f, 0.5f);
    topBorder.backgroundColor    = [UIColor redColor].CGColor;

    CALayer *bottomBorder        = [CALayer layer];
    bottomBorder.frame           = CGRectMake(0.0f, (self.tableView.frame.origin.y + self.tableView.frame.size.height), 320.0f, 0.5f);
    bottomBorder.backgroundColor = [UIColor redColor].CGColor;

    [self.view.layer addSublayer:topBorder];
    [self.view.layer addSublayer:bottomBorder];
}

/*** GET AUTORESIZED FRAME DIMENSIONS ***/
- (void)viewDidLayoutSubviews{
    [self addBorders];
}

Bez wywoływania metody z viewDidLayoutSubviewmetody, tylko górna krawędź jest rysowana poprawnie, ponieważ dolna krawędź jest gdzieś poza ekranem.

Brandon Read
źródło
3
viewDidLayoutSubviews jest wywoływana kilka razy, tworzysz mnóstwo warstw ... Musisz dodać kilka sprawdzeń istnienia przed dodaniem kolejnej warstwy.
Kalzem
Skomentuj kilka lat później ... powinieneś również wywołać tę metodę w superklasie, gdy nadpisujesz, zanim cokolwiek innego:[super viewDidLayoutSubviews];
Alejandro Iván
5

Dla tych, którzy mogą nadal borykać się z takimi problemami, zwłaszcza z TableviewCell.

Po prostu zastąp metodę:

-(void)layoutSubviews
{
//your code here like drawing a shadow
}

W przypadku UITableViewCell lub UICollectionViewCell utwórz podklasę komórki i nadpisz tę samą metodę:

-(void)layoutSubviews
{
//your code here like drawing a shadow
}
Mayank Pahuja
źródło
to jedyny sposób, w jaki mogłem uzyskać ostateczny rozmiar mojego widoku
Benoit Jadinon
To był jedyny sposób, w jaki mogłem uzyskać ostateczny rozmiar dla któregokolwiek z moich ograniczonych widoków, ponieważ zanim próbowałem je wprowadzić na awakeFromNib, co nie dawało subviews czasu na rzeczywistą zmianę rozmiaru. Dziękuję Ci!
Azin Mehrnoosh
3

Użyj -(void)viewWillAppear:(BOOL)animatedi zadzwoń [self.view layoutIfNeeded]; - to działa, próbowałem.

ponieważ jeśli -(void)viewDidLayoutSubviewsgo użyjesz , zadziała na pewno, ale ta metoda jest wywoływana za każdym razem, gdy twój interfejs użytkownika wymaga aktualizacji / zmian. Który będzie trudny do opanowania. Klawisz programowy to użycie zmiennej bool, aby uniknąć takiej pętli wywołań. lepsze wykorzystanie viewWillAppear. Funkcja Remember viewWillAppearzostanie również wywołana, jeśli widok zostanie ponownie załadowany (bez ponownego przydzielania).


źródło
2

Ramka jest nadal aktualna. Ostatecznie widok wykorzystuje swoją właściwość frame do układania się. Oblicza tę klatkę na podstawie wszystkich ograniczeń. Ograniczenia są używane tylko dla początkowego układu (i za każdym razem, gdy layoutSubviews jest wywoływany w widoku, tak jak po obróceniu). Następnie informacje o pozycji znajdują się we właściwości ramki. A może widzisz inaczej?

borrrden
źródło
1
Widzę inaczej. Wartości klatek się nie zmieniają, nawet po bezpośredniej zmianie ograniczeń szerokości / wysokości (poprzez zmianę ich stałych. Mam do nich IBOutlet w xib)
yuf
Mój problem jest wyjątkowy, ponieważ zmieniam ograniczenie, a następnie muszę użyć ramki w tej samej funkcji. Wywołanie setNeedsLayout nie aktualizuje go natychmiast. Myślę, że muszę najpierw poczekać na układ, a następnie użyć ramki. Moje drugie pytanie brzmi: jak mogę zmienić ograniczenie wysokości / szerokości, jeśli nie mam do niego bezpośredniego odniesienia?
yuf
1
Powinieneś wtedy dispatch_async, a to pozwoli ci opóźnić twój kod do następnej klatki. Co do drugiej części ... nie wiem ... musiałbyś powtórzyć wszystkie ograniczenia i poszukać, które z nich ma zastosowanie ... IBOutlety są znacznie łatwiejsze.
borrrden
Prawda dla IBOutlets, ale chcę napisać funkcję w kategorii dla UIView, z którą nie będę mieć IB do pracy.
yuf