在故事板中載入 nib 時 IBOutlet 為 nil
故事板是在 iOS 5 開始出現的,在此之前我們使用的是 nib/xib。一個故事板支援多個 ViewController,同時可以在這些 ViewController 中進行連線(segue)。但是隨著工程中 ViewController 的增加,故事板中 ViewController 之間的連線變得紛亂複雜,故事板也就失去了原來的意義。與之相比較,nib/xib 只支援單一的 ViewController,這種因為簡單帶來的清晰使得它在專案中仍然被大量使用。
但是,如果在專案中混合使用故事板和 nib 檔案,可能會帶來一些意想不到的問題。一個典型的問題是,storyboard 載入 nib 中的 view controller 時,這個 view controller 中的 IBOutlet 都會丟失連線,這樣在 viewDidLoad 後,所有的 IBOutlet 屬性都為 nil,你根本無法使用這些屬性。你可以來看一個例子。
-
新建一個 Single View 專案。
-
新建一個 UIViewController 子類,名字叫做 TestViewController,確認勾上 Also create xib file。
-
開啟 TestViewController.xib 檔案,拖入一個 label,為這個按鈕建立一個到 IBOutlet 屬性 label 的連線。
-
在 TestViewController.m 的 viewDidLoad 方法中,加入這句:
_label.text = @"我是一個 Label";
同時在這句上打一個斷點。
-
開啟 Main.storyboard。在 ViewController 上放一個按鈕。title 設定為“下一步”。
-
拖入一個 UIViewController。 將 Class 改成 TestViewController。
-
使用 Embed in Navigation Contorller,將 View Controller 外面套上一個導航控制器。
-
在 View Controller 和 TestViewController 之間新增一個 segue,ID 不妨叫做 test。
-
為 View Controller 上的“下一步”按鈕建立一個 IBAction buttonClicked,在裡面編寫程式碼:
-(void)buttonClicked{ [self performSegueWithIdentifier:@"test" sender:nil];
這樣,當按鈕一點選,導航控制器會跳到 TestViewController。
Build & run,點選“下一步”按鈕,斷點停下,將滑鼠放在 _label 變數上檢視值,發現值為 nil,繼續執行,Label 上的文字不會改變。
這個問題說明,故事板在載入 nib 檔案時,不會自動載入 nib 檔案中的內容。為了搞清楚故事板是如何載入 TestViewController 類的,我們在 TestViewController.m 中增加兩個初始化方法,並在每個方法中打上斷點:
-(instancetype)initWithCoder:(NSCoder *)aDecoder{
self = [super initWithCoder:aDecoder];
return self;
}
-(instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil{
return [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
}
Build & run,我們發現斷點在 initWithCoder: 方法中停下。說明故事板是呼叫這個方法來初始化 TestViewController 的。
我們將這個方法修改為:
-(instancetype)initWithCoder:(NSCoder *)aDecoder{
self = [super initWithNibName:NSStringFromClass([self class]) bundle:nil];
return self;
}
這樣,故事板就會從 nib 檔案中載入 TestViewController 了。Build & run,當斷點停下,觀察 _label 變數,現在它不再為 nil 了:
繼續執行,TestViewController 上 Label 的文字成功被我們改變成“我是一個 Label”。
當然,我們也可以用程式碼建立 TestViewController, 而不是讓故事板為我們建立 TestViewController。
在 Main.storyboard 中刪除 Test View Controller,然後修改 View Controller 的 buttonClicked 方法:
TestViewController* vc = [TestViewController new];
[self.navigationController pushViewController:vc animated:YES];
效果也是一樣的。