1. UIViewController的布局過程
VC的生命周期的部分過程 :
viewDidLoad
-> viewWillAppear
-> updateViewConstraints
-> viewWillLayoutSubviews
-> viewDidLayoutSubviews
-> viewDidAppear
-> viewWillDisAppear
-> updateViewConstraints
-> viewDidDisAppear
對應View的updateConstraints
-> layoutSubViews
-> drawRect
當view修改約束(addConstraint
敞嗡,removeConstraint
)會觸發(fā)setNeedsUpdateConstraints
许溅,而這個在layoutSubViews之前會觸發(fā)updateConstraints蔬螟,完成之后會調(diào)用layoutSubViews白群。UIViewController在有個updateViewConstraints 方法兄猩,這個方法實際是self.view 被設置了setNeedsUpdateConstraints(第一次展示的時候)炼吴,必然會調(diào)用這個方法(與上面的解釋保持一致了峻仇,第一次可以理解為為self.view增加了各種約束)弓颈。而這個方法的默認實現(xiàn)是調(diào)用子view的updateConstraints方法篮洁,這樣就自上而下的完成了布局涩维。
- 此處需要注意的地方:
- 不要忘記調(diào)用父類的方法,避免有時候出現(xiàn)一些莫名的問題袁波。
- 在view的layoutSubViews或者ViewController的viewDidLayoutSubviews方法里后可以拿到view的實際frame瓦阐,所以當我們真的需要frame的時候需要在這個時間點以后才能拿到。
下面我們可以解釋是為什么viewDidLoad里通過setFrame的方式改過原先在storyboard里拖動的約束代碼無效了篷牌。因為updateViewConstraints在viewDidLoad后執(zhí)行睡蟋,會覆蓋掉之前的設置的frame,所以無效枷颊。
分別打印每個與生命周期相關的代碼戳杀。
// 沒有通過storyBoard(即xib或非xib)調(diào)用這個方法
- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {
NSLog(@"%s", __func__);
}
return self;
}
// 通過storyBoard會調(diào)用這個方法
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
if (self = [super initWithCoder:aDecoder]) {
NSLog(@"%s", __func__);
}
return self;
}
// xib加載完成
- (void)awakeFromNib {
[super awakeFromNib];
NSLog(@"%s", __func__);
}
// 加載視圖(默認從nib)
- (void)loadView {
NSLog(@"%s", __func__);
self.view = [[UIView alloc] initWithFrame:[UIScreen mainScreen].bounds];
self.view.backgroundColor = [UIColor redColor];
}
//視圖控制器中的視圖加載完成,viewController自帶的view加載完成
- (void)viewDidLoad {
NSLog(@"%s", __func__);
[super viewDidLoad];
}
//視圖將要出現(xiàn)
- (void)viewWillAppear:(BOOL)animated {
NSLog(@"%s", __func__);
[super viewWillAppear:animated];
}
// view 即將布局其 Subviews
- (void)viewWillLayoutSubviews {
NSLog(@"%s", __func__);
[super viewWillLayoutSubviews];
}
// view 已經(jīng)布局其 Subviews
- (void)viewDidLayoutSubviews {
NSLog(@"%s", __func__);
[super viewDidLayoutSubviews];
}
//視圖已經(jīng)出現(xiàn)
- (void)viewDidAppear:(BOOL)animated {
NSLog(@"%s", __func__);
[super viewDidAppear:animated];
}
//視圖將要消失
- (void)viewWillDisappear:(BOOL)animated {
NSLog(@"%s", __func__);
[super viewWillDisappear:animated];
}
//視圖已經(jīng)消失
- (void)viewDidDisappear:(BOOL)animated {
NSLog(@"%s", __func__);
[super viewDidDisappear:animated];
}
//出現(xiàn)內(nèi)存警告 //模擬內(nèi)存警告:點擊模擬器->hardware-> Simulate Memory Warning
- (void)didReceiveMemoryWarning {
NSLog(@"%s", __func__);
[super didReceiveMemoryWarning];
}
// 視圖被銷毀
- (void)dealloc {
NSLog(@"%s", __func__);
}
打印結果
-[ViewController initWithCoder:]
-[ViewController awakeFromNib]
-[ViewController loadView]
-[ViewController viewDidLoad]
-[ViewController viewWillAppear:]
-[ViewController viewWillLayoutSubviews]
-[ViewController viewDidLayoutSubviews]
-[ViewController viewWillLayoutSubviews]
-[ViewController viewDidLayoutSubviews]
-[ViewController viewDidAppear:]
-[ViewController viewWillDisappear:]
-[ViewController viewDidDisappear:]
-[ViewController dealloc]
-[ViewController didReceiveMemoryWarning]
函數(shù) | 作用 | 注意事項 |
---|---|---|
initWithNibName:bundle: |
初始化UIViewController夭苗,執(zhí)行關鍵數(shù)據(jù)初始化操作信卡,非StoryBoard創(chuàng)建UIViewController都會調(diào)用這個方法。 | 不要在這里做view相關操作题造,view在loadView方法中才初始化傍菇。 |
initWithCoder: |
如果使用StoryBoard進行視圖管理,程序不會直接初始化一個UIViewController界赔,StoryBoard會自動初始化或在segue被觸發(fā)時自動初始化桥嗤,因此方法initWithNibName:bundle 不會被調(diào)用,但是initWithCoder會被調(diào)用仔蝌。 |
|
awakeFromNib |
當awakeFromNib方法被調(diào)用時泛领,所有視圖的outlet和action已經(jīng)連接,但還沒有被確定敛惊,這個方法可以算作適合視圖控制器的實例化配合一起使用的渊鞋,因為有些需要根據(jù)用戶洗好來進行設置的內(nèi)容,無法存在storyBoard或xib中,所以可以在awakeFromNib方法中被加載進來锡宋。 | |
loadView |
當執(zhí)行到loadView方法時儡湾,如果視圖控制器是通過nib創(chuàng)建,那么視圖控制器已經(jīng)從nib文件中被解檔并創(chuàng)建好了执俩,接下來任務就是對view進行初始化徐钠。 loadView方法在UIViewController對象的view被訪問且為空的時候調(diào)用。這是它與awakeFromNib方法的一個區(qū)別役首。 假設我們在處理內(nèi)存警告時釋放view屬性:self.view = nil尝丐。因此loadView方法在視圖控制器的生命周期內(nèi)可能被調(diào)用多次。 loadView方法不應該直接被調(diào)用衡奥,而是由系統(tǒng)調(diào)用爹袁。它會加載或創(chuàng)建一個view并把它賦值給UIViewController的view屬性。 在創(chuàng)建view的過程中矮固,首先會根據(jù)nibName去找對應的nib文件然后加載失息。如果nibName為空或找不到對應的nib文件,則會創(chuàng)建一個空視圖(這種情況一般是純代碼) |
在重寫loadView方法的時候档址,不要調(diào)用父類的方法盹兢。 |
viewDidLoad |
當loadView將view載入內(nèi)存中,會進一步調(diào)用viewDidLoad方法來進行進一步設置守伸。此時蛤迎,視圖層次已經(jīng)放到內(nèi)存中,通常含友,我們對于各種初始化數(shù)據(jù)的載入,初始設定校辩、修改約束窘问、移除視圖等很多操作都可以這個方法中實現(xiàn)。 視圖層次(view hierachy):因為每個視圖都有自己的子視圖宜咒,這個視圖層次其實也可以理解為一顆樹狀的數(shù)據(jù)結構惠赫。而樹的根節(jié)點,也就是根視圖(root view),在UIViewController中以view屬性故黑。它可以看做是其他所有子視圖的容器儿咱,也就是根節(jié)點。 |
|
viewWillAppear |
系統(tǒng)在載入所有的數(shù)據(jù)后场晶,將會在屏幕上顯示視圖混埠,這時會先調(diào)用這個方法,通常我們會在這個方法對即將顯示的視圖做進一步的設置诗轻。比如钳宪,設置設備不同方向時該如何顯示;設置狀態(tài)欄方向、設置視圖顯示樣式等吏颖。 另一方面搔体,當APP有多個視圖時,上下級視圖切換是也會調(diào)用這個方法半醉,如果在調(diào)入視圖時疚俱,需要對數(shù)據(jù)做更新,就只能在這個方法內(nèi)實現(xiàn)缩多。 |
|
viewWillLayoutSubviews |
view 即將布局其Subviews呆奕。 比如view的bounds改變了(例如:狀態(tài)欄從不顯示到顯示,視圖方向變化),要調(diào)整Subviews的位置瞧壮,在調(diào)整之前要做的工作可以放在該方法中實現(xiàn) | |
viewDidLayoutSubviews |
view已經(jīng)布局其Subviews登馒,這里可以放置調(diào)整完成之后需要做的工作。 | |
viewDidAppear |
在view被添加到視圖層級中以及多視圖咆槽,上下級視圖切換時調(diào)用這個方法陈轿,在這里可以對正在顯示的視圖做進一步的設置。 | |
viewWillDisappear |
在視圖切換時秦忿,當前視圖在即將被移除麦射、或被覆蓋是,會調(diào)用該方法灯谣,此時還沒有調(diào)用removeFromSuperview潜秋。 | |
viewDidDisappear |
view已經(jīng)消失或被覆蓋,此時已經(jīng)調(diào)用removeFromSuperView; | |
dealloc |
視圖被銷毀胎许,此次需要對你在init和viewDidLoad中創(chuàng)建的對象進行釋放峻呛。 | |
didReceiveMemoryWarning |
在內(nèi)存足夠的情況下,app的視圖通常會一直保存在內(nèi)存中辜窑,但是如果內(nèi)存不夠钩述,一些沒有正在顯示的viewController就會收到內(nèi)存不足的警告,然后就會釋放自己擁有的視圖穆碎,以達到釋放內(nèi)存的目的牙勘。但是系統(tǒng)只會釋放內(nèi)存,并不會釋放對象的所有權所禀,所以通常我們需要在這里將不需要顯示在內(nèi)存中保留的對象釋放它的所有權方面,將其指針置nil。 |
生命歷程
-
[ViewController initWithCoder:]
或[ViewController initWithNibName:Bundle]:
首先從歸檔文件中加載UIViewController
對象色徘。即使是純代碼恭金,也會把nil
作為參數(shù)傳給后者。 -
[UIView awakeFromNib]:
作為第一個方法的助手褂策,方法處理一些額外的設置蔚叨。 -
[ViewController loadView]:
創(chuàng)建或加載一個view并把它賦值給UIViewController
的view
屬性床蜘。 -
-[ViewController viewDidLoad]:
此時整個視圖層次已經(jīng)放到內(nèi)存中,可以添加修改視圖蔑水,修改約束邢锯,加載數(shù)據(jù)等。 -
[ViewController viewWillAppear:]:
視圖加載完成搀别,并即將顯示在屏幕上丹擎。還沒設置動畫,可以改變當前屏幕方向或狀態(tài)欄的風格等歇父。 -
[ViewController viewWillLayoutSubviews]:
即將開始子視圖位置布局 -
[ViewController viewDidLayoutSubviews]:
用于通知視圖的位置布局已經(jīng)完成 -
[ViewController viewDidAppear:]:
視圖已經(jīng)展示在屏幕上蒂培,可以對視圖做一些關于展示效果方面的修改。 -
[ViewController viewWillDisappear:]:
視圖即將消失 -
[ViewController viewDidDisappear:]:
視圖已經(jīng)消失'
注意:
- 只有init系列的方法,如
initWithNibName
需要自己調(diào)用榜苫,其他方法如loadView
和awakeFromNib
則是系統(tǒng)自動調(diào)用护戳。而viewWill/Did
系列的方法則類似于回調(diào)和通知,也會被自動調(diào)用垂睬。- self.view是在
loadView
方法中創(chuàng)建并建立聯(lián)系的媳荒,如果想要自定義該view,不要調(diào)用[super loadView]
,并且最后要將自定義的view賦值給self.view
。如果該控制器沒有xib文件驹饺,重寫了loadView但沒有做任何事情(也就是self.view
為空)钳枕,在viewDidLoad
中還使用了self.view
(self.view
為空時會調(diào)用loadView
),這樣會造成死循環(huán)赏壹。- 除了
initWithNibName
和awakeFromNib
方法是處理視圖控制器外鱼炒,其他方法都是處理視圖。這兩個方法在視圖控制器的生命周期里只會調(diào)用一次蝌借。