Jaka jest zależność między setNeedsLayout, layoutIfNeeded i layoutSubviews w UIView?


105

Czy ktoś może dać ostateczne wyjaśnienie relacji pomiędzy UIView's setNeedsLayout, layoutIfNeededi layoutSubviewsmetodami? I przykładowa implementacja, w której zostaną użyte wszystkie trzy. Dzięki.

Wprawia mnie w zakłopotanie, że jeśli wyślę setNeedsLayoutwiadomość do widoku niestandardowego, następną rzeczą, którą wywoła po tej metodzie, będzie layoutSubviewsprzeskakiwanie layoutIfNeeded. Z dokumentów, których spodziewałbym się, że przepływ będzie setNeedsLayout> powodami, layoutIfNeededktóre zostaną nazwane> przyczynami, layoutSubviewsktóre zostaną wywołane.

Odpowiedzi:


104

Nadal próbuję to rozgryźć, więc potraktuj to z pewnym sceptycyzmem i wybacz mi, jeśli zawiera błędy.

setNeedsLayoutjest łatwy: po prostu ustawia flagę gdzieś w UIView, która oznacza, że ​​wymaga układu. To wymusi layoutSubviewswywołanie widoku przed następnym przerysowaniem. Zauważ, że w wielu przypadkach nie musisz wywoływać tego jawnie ze względu na autoresizesSubviewswłaściwość. Jeśli tak jest (a jest to ustawienie domyślne), każda zmiana w ramce widoku spowoduje, że widok rozłoży podglądy.

layoutSubviewsto metoda, w której robisz wszystkie interesujące rzeczy. Jest to odpowiednik drawRectdla układu, jeśli wolisz. Trywialnym przykładem może być:

-(void)layoutSubviews {
    // Child's frame is always equal to our bounds inset by 8px
    self.subview1.frame = CGRectInset(self.bounds, 8.0, 8.0);
    // It seems likely that this is incorrect:
    // [self.subview1 layoutSubviews];
    // ... and this is correct:
    [self.subview1 setNeedsLayout];
    // but I don't claim to know definitively.
}

AFAIK layoutIfNeedednie jest generalnie przeznaczony do zastąpienia w Twojej podklasie. Jest to metoda, że masz na myśli, aby zadzwonić, jeśli chcesz aby być rozplanowane teraz . Implementacja Apple może wyglądać mniej więcej tak:

-(void)layoutIfNeeded {
    if (self._needsLayout) {
        UIView *sv = self.superview;
        if (sv._needsLayout) {
            [sv layoutIfNeeded];
        } else {
            [self layoutSubviews];
        }
    }
}

Nazwałbyś layoutIfNeededna celu zmusić go (i jego superviews jak jest to konieczne), aby być rozplanowane natychmiast.


2
Dziękuję - cieszę się, że ktoś w końcu odpowiedział. W międzyczasie musiałem też zagłębić się w setNeedsDisplay - co powoduje wywołanie drawRect - i contentMode. Tylko contentMode = UIViewContentModeRedraw spowoduje wywołanie setNeedsDisplay w przypadku zmiany granic widoku. Inne opcje contentMode powodują tylko skalowanie widoku, przesuwanie o pewną wartość lub wyrównanie do krawędzi. Mój niestandardowy UITableViewCell nie chciał przerysowywać po zmianach orientacji, tylko skalował się, dopóki nie ustawię contentMode na UIViewContentModeRedraw.
— Tarfa

Myślę, że być może [self.subview1 layoutSubviews] w twoim kodzie powinno zostać zastąpione przez [self.subview1 setNeedsLayout]. Ponieważ layoutSubviews ma być nadpisany, ale nie ma być wywoływany z lub do innego widoku. Albo struktura określa, kiedy ją wywołać (przez zgrupowanie wielu żądań układu w jedno wywołanie w celu zwiększenia wydajności), albo wykonujesz to pośrednio, wywołując layoutIfNeeded gdzieś w hierarchii widoków.
— Tarfa

Korekta mojego powyższego komentarza: "... Ponieważ layoutSubviews ma być nadpisany lub wywołany z siebie, ale nie ma być wywoływany z lub do innego widoku ..."
— Tarfa

Naprawdę nie jestem pewien [subview1 layoutSubviews]. Bardzo dobrze może być tak drawRect, że nie powinieneś dzwonić do tego bezpośrednio. Ale nie jestem pewien, czy wywołanie setNeedsLayoutpodczas fazy układu spowoduje, że widok zostanie ułożony w tej samej fazie układu, czy też zostanie opóźniony do następnej. Byłoby miło zobaczyć odpowiedź od kogoś, kto naprawdę rozumie, jak to wszystko działa ...
— n8gray

34

Chciałbym dodać do odpowiedzi n8gray, że w niektórych przypadkach będziesz musiał zadzwonić, setNeedsLayouta następnie layoutIfNeeded.

Załóżmy na przykład, że napisałeś niestandardowy widok rozszerzający UIView, w którym pozycjonowanie podglądów podrzędnych jest złożone i nie można tego zrobić za pomocą funkcji autoresizeMask lub AutoLayout iOS6. Niestandardowe pozycjonowanie można wykonać przez nadpisanie layoutSubviews.

Jako przykład załóżmy, że masz niestandardowy widok, który ma contentViewwłaściwość i edgeInsetswłaściwość, która umożliwia ustawienie marginesów wokół contentView. layoutSubviewswyglądałoby tak:

- (void) layoutSubviews {
    self.contentView.frame = CGRectMake(
        self.bounds.origin.x + self.edgeInsets.left,
        self.bounds.origin.y + self.edgeInsets.top,
        self.bounds.size.width - self.edgeInsets.left - self.edgeInsets.right,
        self.bounds.size.height - self.edgeInsets.top - self.edgeInsets.bottom); 
}

Jeśli chcesz mieć możliwość animowania zmiany klatki po każdej zmianie edgeInsetswłaściwości, musisz nadpisać edgeInsetsmetodę ustawiającą w następujący sposób i wywołać, setNeedsLayouta następnie layoutIfNeeded:

- (void) setEdgeInsets:(UIEdgeInsets)edgeInsets {
    _edgeInsets = edgeInsets;
    [self setNeedsLayout]; //Indicates that the view needs to be laid out 
                           //at next update or at next call of layoutIfNeeded, 
                           //whichever comes first 
    [self layoutIfNeeded]; //Calls layoutSubviews if flag is set
}

W ten sposób, jeśli wykonasz następujące czynności, jeśli zmienisz właściwość edgeInsets wewnątrz bloku animacji, animowana będzie zmiana klatki elementu contentView.

[UIView animateWithDuration:2 animations:^{
    customView.edgeInsets = UIEdgeInsetsMake(45, 17, 18, 34);
}];

Jeśli nie dodasz wywołania do layoutIfNeeded w metodzie setEdgeInsets, animacja nie zadziała, ponieważ layoutSubviews zostanie wywołany w następnym cyklu aktualizacji, co jest równoznaczne z wywołaniem go poza blokiem animacji.

Jeśli wywołasz tylko layoutIfNeeded w metodzie setEdgeInsets, nic się nie stanie, ponieważ flaga setNeedsLayout nie jest ustawiona.


4
Lub powinieneś być w stanie wywołać [self layoutIfNeeded]w bloku animacji po ustawieniu wstawek krawędzi.
— Scott Fister,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.