UIView'in setNeedsLayout, layoutIfNeeded ve layoutSubviews arasındaki ilişki nedir?


105

Herkes arasındaki ilişki konusunda kesin bir açıklama verebilir UIView's setNeedsLayout,layoutIfNeeded ve layoutSubviewsyöntemlere? Ve üçünün de kullanılacağı örnek bir uygulama. Teşekkürler.

Kafamı karıştıran şey, özel görünümüme bir setNeedsLayoutmesaj gönderirsem layoutSubviews, bu yöntemden sonra çağırdığı bir sonraki şey , hemen atlamaktır layoutIfNeeded. Dokümanlardan akışın setNeedsLayout> layoutIfNeededçağrılma nedenleri> çağrılacak nedenler olmasını beklerdim layoutSubviews.

Yanıtlar:


104

Hâlâ kendim anlamaya çalışıyorum, bu yüzden bunu biraz şüpheyle alın ve hatalar içeriyorsa beni affedin.

setNeedsLayoutkolay olanıdır: UIView'de bir yere, onu gerekli düzen olarak işaretleyen bir bayrak belirler. Bu layoutSubviews, bir sonraki yeniden çizim gerçekleşmeden önce görüşe çağrılmaya zorlanacaktır. Çoğu durumda, autoresizesSubviewsözellik nedeniyle bunu açıkça aramanıza gerek olmadığını unutmayın . Bu ayarlanmışsa (varsayılan olarak budur), bir görünümün çerçevesindeki herhangi bir değişiklik, görünümün alt görünümlerini düzenlemesine neden olur.

layoutSubviewstüm ilginç şeyleri yaptığınız yöntemdir. drawRectİsterseniz, düzen için eşdeğerdir . Önemsiz bir örnek şöyle olabilir:

-(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 layoutIfNeeded, genellikle alt sınıfınızda geçersiz kılınmak için tasarlanmamıştır. İster romantik bir görünüm düzenlenmelidir istediğinizde aramak istedim olduğumuz bir yöntem var elimizde . Apple'ın uygulaması aşağıdaki gibi görünebilir:

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

layoutIfNeededHemen ortaya konması için onu (ve gerektiği şekilde denetlemelerini) zorlamak için bir görüş çağırırsınız .


2
Teşekkür ederim - birisinin sonunda bunu cevapladığına sevindim. Bu arada, drawRect'in çağrılmasına neden olan setNeedsDisplay ve contentMode'u da incelemek zorunda kaldım. Yalnızca contentMode = UIViewContentModeRedraw, bir görünümün sınırları değiştiğinde setNeedsDisplay'in çağrılmasına neden olur. Diğer contentMode seçenekleri yalnızca görünümün ölçeklenmesine, bir miktar kaydırılmasına veya bir kenara hizalanmasına neden olur. Özel UITableViewCell'im yön değişikliklerinde yeniden çizim yapmak istemiyordu, yalnızca contentMode'u UIViewContentModeRedraw olarak ayarlayana kadar ölçeklendirecekti.
Tarfa

Sanırım kodunuzdaki [self.subview1 layoutSubviews] [self.subview1 setNeedsLayout] ile değiştirilmelidir. LayoutSubviews'in geçersiz kılınması amaçlandığından, ancak başka bir görünümden veya başka bir görünüme çağrılması amaçlanmamaktadır. Ya çerçeve onu ne zaman çağıracağını belirler (birden fazla düzen isteğini verimlilik için tek bir çağrıda gruplayarak) ya da dolaylı olarak layoutIfNeeded'i görünüm hiyerarşisinde bir yerde çağırarak yaparsınız.
Tarfa

Yukarıdaki yorumumda düzeltme: "... Düzen alt görünümlerinin geçersiz kılınması veya kendisinden çağrılması amaçlandığından, ancak başka bir görünümden veya başka bir görünümden çağrılması amaçlanmadığından ..."
Tarfa

Ben gerçekten emin değilim [subview1 layoutSubviews]. Çok iyi gibi olabilir ki drawRect, doğrudan çağrı gerekmiyor. Ancak setNeedsLayout, düzen aşamasında aramanın , görünümün aynı düzen aşamasında ortaya çıkmasına veya bir sonrakine kadar ertelenmesine neden olup olmayacağından emin değilim . Tüm bunların nasıl çalıştığını gerçekten anlayan birinin cevabını görmek güzel olurdu ...
n8gray

34

N8gray'in cevabına, bazı durumlarda, setNeedsLayoutardından aramanız gerekeceği cevabını eklemek istiyorum layoutIfNeeded.

Diyelim ki, alt görünümlerin konumlandırmasının karmaşık olduğu ve otomatik yeniden boyutlandırmaMask veya iOS6 Otomatik Yerleşim ile yapılamayacağı, UIView'ı genişleten özel bir görünüm yazdığınızı varsayalım. Özel konumlandırma, geçersiz kılarak yapılabilirlayoutSubviews .

Örnek olarak, contentView etrafındaki marjları ayarlamaya izin veren bir contentViewözelliği ve edgeInsetsözelliği olan özel bir görünüme sahip olduğunuzu varsayalım. layoutSubviewsşöyle görünürdü:

- (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); 
}

edgeInsetsÖzelliği her değiştirdiğinizde çerçeve değişikliğini canlandırabilmek istiyorsanız , edgeInsetsaşağıdaki gibi ayarlayıcıyı geçersiz kılmanız ve setNeedsLayoutardından şunu çağırmanız gerekir 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
}

Bu şekilde, aşağıdakini yaparsanız, bir animasyon bloğunun içindeki edgeInsets özelliğini değiştirirseniz, contentView'in çerçeve değişikliğine animasyon uygulanacaktır.

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

SetEdgeInsets yönteminde layoutIfNeeded çağrısını eklemezseniz, layoutSubviews sonraki güncelleme döngüsünde çağrılacağı için animasyon çalışmayacaktır, bu da onu animasyon bloğunun dışına çağırmaya eşittir.

SetEdgeInsets yönteminde yalnızca layoutIfNeeded öğesini çağırırsanız, setNeedsLayout bayrağı ayarlanmadığından hiçbir şey olmaz.


4
Veya [self layoutIfNeeded]kenar girintilerini ayarladıktan sonra animasyon bloğu içinde arama yapabilmelisiniz .
Scott Fister
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.