前言
看似常用的UIView,其實有很多不經常用到的方法和應該注意的機制,我是一個喜歡打破砂鍋問到底的人,可是很多問題在網上搜索不到答案,大部分的博客都是轉載相同的東西,所以我自己就新建 Dome,實際測試了一些數據,得到了一些有用的東西,分享給大家,其中也用到了其它大神的理論和指點,在此感謝。
viewController的生命周期
-
單個viewController的生命周期
initWithCoder:(NSCoder *)aDecoder:(如果使用storyboard或者xib) loadView:#加載view 會多次調用并且會使viewWillLayoutSubviews、viewDidLayoutSubviews不再執行 viewDidLoad:view加載完畢 viewWillAppear:控制器的view將要顯示, viewWillLayoutSubviews:控制器的view將要布局子控件 viewDidLayoutSubviews:控制器的view布局子控件完成 viewDidAppear:控制器的view完全顯示 viewWillDisappear:控制器的view即將消失的時候 viewDidDisappear:控制器的view完全消失的時候
-
多個viewControllers跳轉
當我們點擊push的時候首先會加載下一個界面然后才會調用界面的消失方法 initWithCoder:(NSCoder *)aDecoder:ViewController2 (如果用xib創建的情況下) loadView:ViewController2 viewDidLoad:ViewController2 viewWillDisappear:ViewController1 將要消失 viewWillAppear:ViewController2 將要出現 viewWillLayoutSubviews ViewController2 viewDidLayoutSubviews ViewController2 viewWillLayoutSubviews:ViewController1 viewDidLayoutSubviews:ViewController1 viewDidDisappear:ViewController1 完全消失 viewDidAppear:ViewController2 完全出現
小結:
整個控制器聲明周期: viewDidLoad -> viewWillAppear -> viewWillLayoutSubviews -> viewDidLayoutSubviews -> viewDidAppear -> viewWillDisappear -> viewDidDisappear說明
viewWillLayoutSubviews 在 viewWillAppear 之后 viewDidAppear 之前執行,這個方法會被調用多次,如果在此創建視圖,可能會創建多個,而且這個方法中執行耗時操作依然會造成跳轉卡頓的問題。
一個VC 在執行viewWillDisappear 或者viewDidDisappear 方法時都是出棧之后才執行的,也就是說 已經不在 self.navigationController 的viewControllers中了,而一個 VC 執行viewWillAppear 時必定是 self.navigationController 的 topViewController即棧頂的視圖。
一個很常見的問題,我們需要在VC被銷毀的時候處理一些事情,可是 viewWillDisappear 不管是入棧新的視圖還是出棧當前視圖都會被調用,無法判斷,結合我之前說的原理,我們可以利用下面這個方法判斷當前VC是否是被出棧了。
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:YES];
if ([[self.navigationController viewControllers] containsObject:self ]) {
NSLog(@"TwoViewController 暫時消失");
}else
{
NSLog(@"TwoViewController 出棧了");
}
}
首先需要特別說明的一點是:
在使用 VC繪制UI的時候,它不同于 UIView,UIView中 init方法調用后,UIView就已經繪制好(初始化好了)。
但是在VC中,當前UIView的是在 viewDidLoad 執行后才初始化好的,所以我們會見到這樣一個Bug,在VC初始化語句后面調用 一個 VC的繪制UI的方法,你會發現根本沒有效果,這就是因為,VC中跟試圖的初始化是需要時間的,你在VC根試圖初始化之前調用繪制UI的方法當然是不會有效果出現的。
UIView的setNeedsDisplay和setNeedsLayout方法。
首先兩個方法都是異步執行的。setNeedsDisplay會調用 drawRect方法,這樣可以拿到UIGraphicsGetCurrentContext,就可以畫畫。
setNeedsLayout會默認調用layoutSubViews,就可以處理子視圖中的一些數據。
綜上兩個方法都是異步執行的,layoutSubviews方便數據計算,drawRect方便視圖重繪。UIView中方法的執行順序大概是這樣的:
init 方法 >>屬性的set/get 方法>> layoutSubviews(當然是要達到觸發條件)>>drawRect, 使用得當可以在View的調用中起到很好的效果。
layoutSubviews在以下情況下會被調用:
1、init初始化不會觸發layoutSubviews。
2、addSubview會觸發layoutSubviews。
3、設置view的Frame會觸發layoutSubviews,當然前提是frame的值設置前后發生了變化。
4、滾動一個UIScrollView會觸發layoutSubviews。
5、旋轉Screen會觸發父UIView上的layoutSubviews事件。
6、改變一個UIView大小的時候也會觸發父UIView上的layoutSubviews事件。
7、直接調用setLayoutSubviews。
drawRect在以下情況下會被調用:
1、如果在UIView初始化時沒有設置rect大小,將直接導致drawRect不被自動調用。drawRect 掉用是在Controller->loadView, Controller->viewDidLoad 兩方法之后掉用的.所以不用擔心在 控制器中,這些View的drawRect就開始畫了.這樣可以在控制器中設置一些值給View(如果這些View draw的時候需要用到某些變量 值).
2、該方法在調用sizeToFit后被調用,所以可以先調用sizeToFit計算出size。然后系統自動調用drawRect:方法。
3、通過設置contentMode屬性值為UIViewContentModeRedraw。那么將在每次設置或更改frame的時候自動調用drawRect:。
4、直接調用setNeedsDisplay,或者setNeedsDisplayInRect:觸發drawRect:,但是有個前提條件是rect不能為0。以上1,2推薦;而3,4不提倡
drawRect方法使用注意點:
1、 **若使用UIView繪圖,只能在drawRect:方法中獲取相應的contextRef并繪圖。如果在其他方法中獲取將獲取到一個invalidate 的ref并且不能用于畫圖**。**drawRect:方法不能手動顯示調用,必須通過調用setNeedsDisplay 或 者 setNeedsDisplayInRect,讓系統自動調該方法。強行調用也不會起作用的。**
2、**若使用calayer繪圖,只能在drawInContext: 中(類似魚drawRect)繪制**,或者在delegate中的相應方法繪制。同樣也是調用setNeedDisplay等間接調用以上方法
3、**若要實時畫圖,不能使用gestureRecognizer,只能使用touchbegan等方法來掉用setNeedsDisplay實時刷新屏幕。**
值得注意的是:
viewWillAppear 嚴謹的說應該是視圖將要達到棧頂時調用,不是每次視圖出現在屏幕時就會調用,一個很常見的現象是,home鍵后,再次啟動程序,第一個出現的頁面的 viewWillAppear 是不回被調用的。