2010-05-10 37 views
103

任何人都可以對UIView'ssetNeedsLayout,layoutIfNeededlayoutSubviews方法之間的關係給出明確的解釋嗎?還有一個使用全部三個的示例實現。謝謝。UIView的setNeedsLayout,layoutIfNeeded和layoutSubviews之間的關係是什麼?

讓我感到困惑的是,如果我發送我的自定義視圖setNeedsLayout消息,那麼它在此方法之後調用的第二件事是layoutSubviews,跳過layoutIfNeeded。從文檔我預計流程爲setNeedsLayout>原因layoutIfNeeded被稱爲>原因layoutSubviews被調用。

回答

101

我仍然試圖弄清楚自己,所以帶着一些懷疑和原諒我,如果它包含錯誤。

setNeedsLayout很簡單:它只是在UIView的某個地方設置一個標誌,標誌着它需要佈局。這將迫使layoutSubviews在下一次重繪發生之前在視圖上被調用。請注意,在許多情況下,由於autoresizesSubviews屬性,您不需要明確地調用它。如果已設置(默認情況下),則對視圖框架的任何更改都會導致視圖佈置其子視圖。

layoutSubviews是你做所有有趣的東西的方法。如果你願意的話,它相當於drawRect的佈局。一個簡單的例子可能是:

-(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一般不意味着在子類中被覆蓋。這是一種當您想要放置視圖時打算打電話給您的方法。蘋果的實現可能是這個樣子:

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

你會打電話layoutIfNeeded上以迫使它(以及它的superviews必要)立即佈置。

+2

謝謝 - 很高興有人終於回答了這個問題。在此期間,我還必須深入研究setNeedsDisplay - 它會導致drawRect被調用 - 和contentMode。只有contentMode = UIViewContentModeRedraw將導致setNeedsDisplay在視圖邊界更改時被調用。其他contentMode選項只會導致視圖縮放,移動一定量,或者與邊緣對齊。我的自定義UITableViewCell不想重新定位方向更改,它只會縮放,直到我將contentMode設置爲UIViewContentModeRedraw。 – Tarfa 2010-05-29 13:08:05

+0

我想你的代碼中的[self.subview1 layoutSubviews]應該替換爲[self.subview1 setNeedsLayout]。由於layoutSubviews意味着被覆蓋,但並不意味着從另一個視圖調用。框架決定何時調用它(通過將多個佈局請求分組爲一個調用以提高效率),或者通過在視圖層次結構中的某個位置調用layoutIfNeeded來間接執行此操作。 – Tarfa 2010-05-29 13:20:25

+0

更正以上我的評論:「...由於layoutSubviews是意圖被覆蓋或從自己調用,但並不意味着被調用或從另一個視圖...」 – Tarfa 2010-05-29 13:29:01

33

我想補充一下n8gray的回答,在某些情況下,您需要撥打setNeedsLayout然後layoutIfNeeded

比方說,您編寫了一個擴展UIView的自定義視圖,其中子視圖的位置很複雜,無法使用autoresizingMask或iOS6 AutoLayout完成。定製定位可以通過覆蓋layoutSubviews來完成。

舉個例子,假設您有一個自定義視圖,該視圖具有contentView屬性和edgeInsets屬性,該屬性允許設置contentView周圍的邊距。 layoutSubviews應該是這樣的:

- (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屬性動畫幀的變化,你需要重寫edgeInsets setter方法如下,並呼籲其次layoutIfNeededsetNeedsLayout

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

這樣,如果您執行以下操作,如果更改動畫塊內的edgeInsets屬性,則contentView的框架更改將爲動畫。

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

如果不添加調用layoutIfNeeded在setEdgeInsets方法,動畫將無法工作,因爲layoutSubviews將在下一個更新週期,這相當於調用它的動畫塊外調用。

如果只調用setEdgeInsets方法中的layoutIfNeeded,則不會設置setNeedsLayout標誌。

+4

或者您應該只需在設置邊緣插圖後在動畫塊內調用'[self layoutIfNeeded]''。 – 2016-09-16 22:16:12

相關問題