本文將簡要討論以下幾個問題:
1、UIView的drawRect方法的調(diào)用機制及注意點
2、UIView的layoutSubviews、layoutIfNeeded、setNeedsLayout等方法的調(diào)用機制
3、如何通過更新view的約束值來實現(xiàn)動畫效果
重繪機制 - drawRect
- 方法定義
Drawing and Updating the View
-drawRect:
Draws the receiver’s image within the passed-in rectangle.
-setNeedsDisplay
Marks the receiver’s entire bounds rectangle as needing to be redrawn.
-setNeedsDisplayInRect:
Marks the specified rectangle of the receiver as needing to be redrawn.
- (void)drawRect:(CGRect)rect //重寫此方法,執(zhí)行重繪任務
- (void)setNeedsDisplay //標記為需要重繪,異步調(diào)用drawRect
- (void)setNeedsDisplayInRect:(CGRect)rect //標記為需要局部重繪
重繪操作是在drawRect方法中完成,但是蘋果不建議直接調(diào)用drawRect方法,當然如果你強制直接調(diào)用此方法是沒有效果的。蘋果要求我們調(diào)用UIView類中的setNeedsDisplay方法,則程序會自動調(diào)用drawRect方法進行重繪。
- 調(diào)用機制
drawRect調(diào)用是在Controller->loadView,Controller->viewDidLoad 兩方法之后調(diào)用的。所以不用擔心在控制器中,這些View的drawRect就開始畫了。這樣可以在控制器中設置一些值給View(如果這些View draw的時候需要用到某些變量值).
1、如果在UIView初始化時沒有設置rect大小,將直接導致drawRect不被自動調(diào)用。
2、該方法在調(diào)用sizeThatFits后被調(diào)用,所以可以先調(diào)用sizeToFit計算出size。然后系統(tǒng)自動調(diào)用drawRect:方法。(sizeToFit會自動調(diào)用sizeThatFits方法)
3、通過設置contentMode屬性值為UIViewContentModeRedraw。那么將在每次設置或更改frame的時候自動調(diào)用drawRect:
4、直接調(diào)用setNeedsDisplay,或者setNeedsDisplayInRect:觸發(fā)drawRect:,但是有個前提條件是rect不能為0
(以上1、2推薦,3、4不提倡)
- drawRect方法使用注意點
1、 若使用UIView繪圖,只能在drawRect:方法中獲取相應的contextRef并繪圖。如果在其他方法中獲取將獲取到一個invalidate 的ref并且不能用于畫圖。drawRect:方法不能手動顯示調(diào)用,必須通過調(diào)用setNeedsDisplay 或 者 setNeedsDisplayInRect,讓系統(tǒng)自動調(diào)該方法。
2、若使用calayer繪圖,只能在drawInContext: 中(類似于drawRect)繪制,或者在delegate中的相應方法繪制。同樣也是調(diào)用setNeedDisplay等間接調(diào)用以上方法。
3、若要實時畫圖,不能使用gestureRecognizer,只能使用touchbegan等方法來調(diào)用setNeedsDisplay實時刷新屏幕
4、隨意使用drawRect可能引入內(nèi)存暴增的問題,參考內(nèi)存惡鬼drawRect
布局刷新
如果你的APP沒有用 Auto Layout,下面的方法可以實現(xiàn)手動 Layout
- (void)layoutSubviews //對subviews重新布局
- (void)layoutIfNeeded //如果有標記則立即重新布局
- (void)setNeedsLayout //標記為需要重新布局
- 調(diào)用機制
1、init初始化不會觸發(fā)layoutSubviews。
2、addSubview會觸發(fā)layoutSubviews。
3、設置view的Frame會觸發(fā)layoutSubviews,當然前提是frame的值設置前后發(fā)生了變化。
4、滾動一個UIScrollView會觸發(fā)layoutSubviews。
5、旋轉(zhuǎn)Screen會觸發(fā)父UIView上的layoutSubviews事件。
6、改變一個UIView大小的時候也會觸發(fā)父UIView上的layoutSubviews事件。
7、直接調(diào)用setNeedsLayout。
在蘋果的官方文檔中強調(diào):You should override this method only if the autoresizing behaviors of the subviews do not offer the behavior you want.
layoutSubviews, 當我們在某個類的內(nèi)部調(diào)整子視圖位置時,需要調(diào)用。
反過來的意思就是說:如果你想要在外部設置subviews的位置,就不要重寫。
- 刷新子對象布局
- (void)layoutSubviews:這個方法,默認沒有做任何事情,需要子類進行重寫
- (void)setNeedsLayout: 標記為需要重新布局,異步調(diào)用layoutIfNeeded刷新布局,不立即刷新,但layoutSubviews一定會被調(diào)用
- (void)layoutIfNeeded:如果有需要刷新的標記,立即調(diào)用layoutSubviews進行布局(如果沒有標記,不會調(diào)用layoutSubviews)
如果要立即刷新,要先調(diào)用[view setNeedsLayout],把標記設為需要布局,然后馬上調(diào)用[view layoutIfNeeded],實現(xiàn)布局
在視圖第一次顯示之前,標記總是“需要刷新”的,可以直接調(diào)用[view layoutIfNeeded]。
- (void)updateConstraintsIfNeeded:立即觸發(fā)約束更新,自動更新布局
- (void)updateConstraints:自定義view應該重寫此方法在其中建立constraints.
注意:要在實現(xiàn)最后調(diào)用[super updateConstraints]
- (BOOL)needsUpdateConstraints:constraint-based layout system使用此返回值去決定是否需要調(diào)用updateConstraints作為正常布局過程的一部分
- (void)setNeedsUpdateConstraints:當一個自定義view的某個屬性發(fā)生改變,并且可能影響到constraint時,需要調(diào)用此方法去標記constraints需要在未來的某個點更新,系統(tǒng)然后調(diào)用updateConstraints
UIView動畫
注意:如果你通過改變約束值來實現(xiàn)動畫效果,然而發(fā)現(xiàn)并沒有動畫,那可能是你沒有調(diào)用layoutIfNeeded方法。
舉個栗子:在ViewController的View上添加一個subView和button并添加相關(guān)約束,通過點擊button更改subView的約束值來實現(xiàn)動畫效果,代碼如下:
[UIView animateWithDuration:0.5 animations:^{
self.leftConstr.constant = 200;//初始約束值為20
} completion:nil];
上面的代碼是看不到動畫效果的,你應該這樣寫:
self.leftConstr.constant = 200;
[UIView animateWithDuration:0.5 animations:^{
[self.view layoutIfNeeded];
} completion:nil];
這是因為self.leftConstr.constant = 200只執(zhí)行了setNeedsLayout方法標記了需要重新布局,但沒有立即執(zhí)行。相反,如果一些變化不想動畫,可以在動畫方法前執(zhí)行layoutIfNeeded方法。