很多時候哥比較喜歡用程式碼新增檢視,特別是要同時加很多UIView時,而且跟 xib 比起來程式碼更容易管理,在多人的專案中程式碼不容易 conflict。
但小牛哥最近發現很多新人都不太清楚正確的使用方法,以下是哥的一些總結,有何不妥歡迎大家一起討論:
(前提條件是這樣的:有一個 View Controller 和相應的 xib 檔案,我們需要為這個controller 動態加上其他的子檢視)
UIViewController 中動態新增 UIView 正確的步驟應該是:
1. 在 viewDidLoad 中建立要新增的 UIView (UILabel, UIImageView, UIButton 等等)。像這樣: UIButton *aButton = [[UIButton alloc] initWithFrame:…] 為什麼不能在 viewWillAppear 中建立?根據蘋果的文件,這裡是新增 last minute 修改的地方,比如修改檢視的位置,顏色等等。如果在這裡建立很多檢視,會導致顯示延遲。
2. 建立的時候最好為每個 UIView 加上約束(NSLayoutConstraint),這樣在不同大小的螢幕中都可以正確顯示。
3. 不用約束也行,必須在 viewDidLayoutSubviews 中修改檢視的 frame。
對於一些簡單的檢視確實沒必要加上約束,但是沒有約束會導致檢視在不同大小的螢幕中的 frame 不好看,這時就得在這裡修改 frame, 對,只能在這裡: viewDidLayoutSubviews 裡修改。
為什麼呢?
首先我們來複習一下 UIViewController 的生命週期:
A: init…
B: loadView
C: viewDidLoad
D: viewWillAppear
E: viewWillLayoutSubviews
F: viewDidLayoutSubviews
G: viewDidAppear
H: viewWillDisappear
I: viewDidDisappear
J: viewDidUnload (not used any more)
K: dealloc…
現在我們可以做個實驗: 在專案中選一個View Contorller ,它的 xib 中的檢視大小為 600×600, 在其 .m 檔案中以上的 C, D,E,F,G方法列印出檢視的frame,像這樣:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
- (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view from its nib. NSLog(@"%s self.view.frame: %@", __PRETTY_FUNCTION__, NSStringFromCGRect(self.view.frame)); } -(void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; NSLog(@"%s self.view.frame: %@", __PRETTY_FUNCTION__, NSStringFromCGRect(self.view.frame)); } -(void)viewWillLayoutSubviews { NSLog(@"%s self.view.frame: %@", __PRETTY_FUNCTION__, NSStringFromCGRect(self.view.frame)); } -(void)viewDidLayoutSubviews { NSLog(@"%s self.view.frame: %@", __PRETTY_FUNCTION__, NSStringFromCGRect(self.view.frame)); } -(void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; NSLog(@"%s self.view.frame: %@", __PRETTY_FUNCTION__, NSStringFromCGRect(self.view.frame)); } |
選擇裝置為 iPhone 5, 執行程式後會得到類似這樣的結果:
1 2 3 4 5 6 7 8 9 |
[TaskDetailsViewController viewDidLoad] self.view.frame: {{0, 0}, {600, 600}} [TaskDetailsViewController viewWillAppear:] self.view.frame: {{0, 0}, {600, 600}} [TaskDetailsViewController viewWillLayoutSubviews] self.view.frame: {{0, 0}, {320, 568}} [TaskDetailsViewController viewDidLayoutSubviews] self.view.frame: {{0, 0}, {320, 568}} [TaskDetailsViewController viewDidAppear:] self.view.frame: {{0, 0}, {320, 568}} |
大家可以看到,一個檢視的大小是在呼叫 viewWillLayoutSubviews 時才會根據裝置而改變,不過在 IOS 8 中,要到viewDidLayoutSubviews 才正確。根檢視的大小改變了,子檢視必須相應做出調整才可以正確顯示,這就是為什麼要在 viewDidLayoutSubviews 中調整動態檢視的frame。
By the way,在 IOS 9 中,根檢視控制元件(Root View Controller)的檢視大小在 viewDidLoad 中就已經正確了,蘋果好像會不時改變這些特點,比如會把系統鍵盤的檢視優先順序提高等等。所以小牛哥覺得動態新增檢視最安全的方法是,建立檢視後馬上加上約束,不管日後蘋果怎麼改都可以正確顯示。
關於如何動態新增約束,大家可以看看這裡:
http://matthewmorey.com/creating-uiviews-programmatically-with-auto-layout/