1. 程式人生 > >iOS storyBoard全解析

iOS storyBoard全解析

(Storyboard)是一個能夠節省你很多設計手機App介面時間的新特性,下面,為了簡明的說明Storyboard的效果,我貼上本教程所完成的Storyboard的截圖: 
 
現在,你就可以清楚的看到這個應用究竟是幹些什麼的,也可以清楚的看到其中的各種關係,這就是Storyboard的強大之處了。如果你要製作一個頁面很多很複雜的App,Storyboard可以幫助你解決寫很多重複的跳轉方法的麻煩,節省很多時間,以便你能夠完全的專注於核心功能的實現上。 



開始 



首先啟動Xcode,新建一個工程,我們在這裡使用Single View App Template,這個模板會提供一個類和一個Storyboard,免去我們自己建立的麻煩。 
 
建立完成之後,Xcode的介面大概是這樣的: 
 
這個新的工程由兩個類:AppDelegate和ViewController以及一個Storyboard組成(如果你選擇了兩個裝置會有兩個Storyboard),注意這個專案沒有xib檔案,讓我們首先看看Storyboard是什麼樣的,雙擊Storyboard開啟他: 
 
Storyboard的樣子和工作方式都和Interface Builder(以下簡稱為IB)像極了,你可以從左下方的控制元件庫中拖動控制元件到你的View之中並且組織他們的排放順序,唯一不同的地方就是,Storyboard不止是包含一個檢視控制元件,而是所有的檢視控制元件以及他們之間的關係。 


Storyboard對一個檢視的官方術語是一個場景,但是一個場景其實就是一個ViewController,在iPhone中一次只能夠展示一個場景,而在iPad中一次可以展示多個場景,比如Mail應用程式。 

通過嘗試新增一些控制元件,你可以感受一下Storyboard的工作方式。 
 
這個是資料顯示器,顯示所有場景及其控制元件的結構。 
 
在IB中,這個位置顯示的是你的NIB檔案中的檔案,而在Storyboard中這裡顯示的是ViewController,目前這裡只有一個ViewController,我們接下來可能會增加一些。 

這是一個文件管理器的縮小版,叫做dock。 
 
Dock展示場景中第一級的控制元件,每個場景至少有一個ViewController和一個FirstReponder,但是也可以有其他的控制元件,Dock還用來簡單的連線控制元件,如果你需要向ViewController傳遞一個關係時,只需要將其按住Ctrl鍵拖到ViewController上就可以了。 

Note:你大概不會太長使用FirstResponder,因為它只是一個代理控制元件,代表著當前你所使用的控制元件。 

現在執行這個應用,他會向我們設計的介面一樣。 
 
如果你以前製作過NIB型的應用的話,你也許回去尋找MainWindow.xib ,這個檔案包括所有的ViewController,Appdelegate等等,但是在Storyboard中這個特性已經被廢止了。 
 
那麼,沒有這個檔案,應用從那裡起始呢? 

讓我們開啟AppDelegate檔案,看看那上面是怎麼說的: 
  1. #import <UIKit/UIKit.h>  
  2. @interface AppDelegate : UIResponder <UIApplicationDelegate>  
  3. @property (strong, nonatomic) UIWindow *window;  
  4. @end  

如果要使用Storyboard特性,那麼AppDelegate必須繼承自UIResponder類, 之前則是繼承自NSObject類的,而且必須有一個不是UIOutlet類的Window屬性宣告才可以。 

如果你再去看AppDelegate的執行檔案,裡面大概什麼都沒有,甚至連 application:didFinishLaunchingWithOptions: 也只是返回了一個 YES,而之前,這裡則需宣告一個ViewController並且將他設定成起始頁面,但是現在這些都沒有了。 

祕密就在info.plist檔案中, 開啟Ratings-Info.plist (在 Supporting Files group裡) 你就會看到這些: 
 
在NIB為UI的應用裡,info.plist檔案中有一個鍵兼做NSMainNibFile,或者叫做Main nib file base name,他用來指示UIApplication載入MainWindow.xib,並且將他與應用連結起來,而現在這個鍵值消失了。 

而Storyboard應用則利用 UIMainStoryboardFile,或者 “Main storyboard file base name” 鍵值來表示當App初始化時的Storyboard名稱,當程式執行時,UIApplication會使用MainStoryboard.sotryboard作為第一載入項,並且將他的UIWindow展示在螢幕上,不需要任何程式設計工作。 

在專案總結面板上,你也可以看到並且編輯這些資訊: 
 
如果你還想設定nib檔案的話,另外有地方去設定的。 

為了完成這個實驗性的小程式,我們開啟main.m,加入 
  1. #import <UIKit/UIKit.h>  
  2. #import "AppDelegate.h"  
  3. int main(int argc, char *argv[])  
  4. {  
  5.     @autoreleasepool {  
  6.         return UIApplicationMain(argc, argv, nil,  
  7.             NSStringFromClass([AppDelegate class]));  
  8.     }  
  9. }  

之前是UIApplicationMain()的函式現在是空的, 變成了 NSStringFromClass([AppDelegate class]). 

與之前使用MainWindow.xib的一個最大的不同是:現在app delegate已經不是Storyboard的一部分了,這是因為app delegate不再從nib檔案中,而侍從Storyboard中載入了,我們必須告訴 UIApplicationMain 我們的app delegate類的名字是什麼,否則他將無法找到。 





製作一個Tab型別的應用 

本教程中的Rating App擁有兩個Tab,在Storyboard中,很輕鬆就能夠做出一個Tab檢視。 

回到MainStoryboard.storyboard中,直接從左邊的Library拖進來一個TabViewController就可以了。 
 
新的Tab Bar Controller附帶了兩個View controller,分別作為Tab的檢視使用,UITabBarController被稱為包含檢視,因為他包含這其他一些View,其他常見的包含檢視還有那vi嘎提鷗鳥 Controller和SplitView Controller。 

在iOS 5中,你還可以自己寫一個自定義的Controller,這在以前是做不到的。 

包含關係在Storyboard中用一下這種箭頭表示。 
 
拉一個Label控制元件到第一個子試圖中,命名為“First Tab”,再在第二個子檢視中新增一個Label,命名為“Second Tab”。 

注意:當螢幕的縮放大於100%時,你無法在單個場景中新增控制元件。 

選中Tab Bar Controller,進入屬性檢查器,選中“作為起始場景”,如下圖: 
 
現在那個沒有頭的虛虛的小箭頭指向了Tab Bar Controller,說明他是起始場景。 
 
這意味著,當你啟動這個應用的時候,UIApplication將會將這個場景作為應用的主螢幕。 

Storyboard一定要有一個場景是起始場景才行。 

現在執行試試吧 
 
code專門為創造這種Tab Bar的應用準備了一個模板,我們也可以使用他,但是自己有能力不用模板自己做一個Tab Bar也是不錯的事。 

如果你添加了多於五個子檢視到一個TabBarcontroller的話,並不會創造五個Tab,第四個tab會自動變成More標籤,不錯吧 




製作一個表格檢視 

目前連線到Tab bar Controller的檢視都是普通的View Controller,現在,我要用一個TableViewController來代替其中的一個ViewController。 

單擊第一個檢視並刪除,從Library中拖出一個TableViewController。 
 
在選中這個TableViewController的前提下,從Library中拖出一個NavController,將會直接附著在上面。 
 
當然也可以調換順序,我完全沒意見。 

由於NavController和TabBarController一樣也是一個包含控制器檢視,所以他也必須包含另一個檢視,你可以看到同樣的箭頭連線者這兩個View。 
 
請注意所有巢狀在NavController下的View都會有一個Navigation Bar,你無法移除它,因為他是一個虛擬的Bar。 

如果你檢視屬性檢測器,你就會發現所有bar的屬性都在一起: 
 
“Inferred”是Storyboard中的預設設定,他意味著繼承的關係,但是你也可以改變他。但是請注意這些設定都是為了讓你更好的進行設計和這樣設定的,隨意修改預設設定會帶來不可遇見的後果,施主自重。 

現在讓我們把這個新的場景連線到Tab Bar Controller中,按住Ctrl拖動,或者右鍵。 
 
當你放手的時候,一個提示框會出現。 
 
當然是選第一個了,Relationship – viewControllers ,這將自動建立兩個場景之間的關係。 
 
 
直接拖動就可以改變Tab Item的順序,同時也會改變顯示Tab的順序,放在最左邊的Tab會第一個顯示。 

現在執行試試看吧 
 
在我們在這個應用中加入任何實質性的功能之前,我們先來清理一下Storyboard,你不需要改變TabBarController中的任何內容而只需要改變他的子檢視就可以了。 

每當你連線一個新的檢視到TabBarController中的時候,他就會自動增加一個Tab Item,你可以使用他的子檢視來修改該Item的圖片和名稱。 

在NavController中選中Tab Item並且在屬性編輯其中將其修改為Player。 
 
將第二個Tab Item命名為“Gesture” 

我們接下來把自定義的圖片加入到這些item中, 原始碼 中包含一個名為“Image”的資料夾,在那裡你可以找到我們用到的資源。 

接下來,將NavController的title改為Player,也可以使用程式碼·· 
 
執行看一看,難以置信吧,你到現在也沒寫一條程式碼。 
 





原型表格單元 

你也許已經注意到了,自從我們加入了Table View Controller之後,Xcode便會現實下面這樣一條警告。 
 
這條警告是:“Unsupported Configuration: Prototype table cells must have reuse identifiers”意思是,原型表格單元必須有一個身份證(意譯啦) 

原型單元格是另一個Storyboard的好特性之一。在之前,如果你想要自定義一個Table Cell,那麼你就不得不用程式碼來實現,要麼就要單獨建立一個Nib檔案來表示單元格內容,現在你也可以這樣做,不過原型單元格可以幫你把這一過程大大的簡化,你現在可以直接在Storyboard設計器中完成這一過程。 

Table View現在預設的會帶有一個空白的原型單元格,選中他,在屬性控制器中將他的Style改為subtitle,這樣的話,每一格就會有兩行字。 
 
 
將附件設定為Disclosure Indicator並且將這個原型單元格的Reuse Identifier 設定喂“PlayerCell”,這將會解決Xcode所警告的問題。 

試著執行一個,發現什麼都沒變,這並不奇怪,因為我們還沒有給這個表格設定一個數據來源(DataSource),用以顯示。 

新建一個檔案,使用UIViewContoller模板,命名為 PlayersViewController ,設定喂UITableViewController的子類,不要勾選建立XIB檔案。 

回到Storyboard編輯器,選擇Table View Controller,在身份控制器中,把他的類設定為PlayerViewController,這對於把Storyboard中的場景和你自定義的子類掛鉤是十分重要的。要是不這麼做,你的子類根本沒用。 
 
現在起,當你執行這個應用時,table view controller其實是PlayersViewContoller的一個例項。 

在 PlayersViewController.h 中宣告一個MutableArray(可變陣列) 
  1. #import <UIKit/UIKit.h>  
  2. @interface PlayersViewController : UITableViewController  
  3. @property (nonatomic, strong) NSMutableArray *players;  
  4. @end  

這個陣列將會包含我們的應用的主要資料模型。我們現在加一些東西到這個陣列之中,新建一個使用Obj-c模板的檔案,命名為player,設定喂NSObject的子類,這將會作為陣列的資料容器。 

編寫Player.h如下: 
  1. @interface Player : NSObject  
  2. @property (nonatomic, copy) NSString *name;  
  3. @property (nonatomic, copy) NSString *game;  
  4. @property (nonatomic, assign) int rating;  
  5. @end  

編寫Player.m如下: 
  1. #import "Player.h"  
  2. @implementation Player  
  3. @synthesize name;  
  4. @synthesize game;  
  5. @synthesize rating;  
  6. @end  

這裡沒有什麼複雜的,Player類只是一個容器罷了,包含三個內容:選手的名字、專案和他的評級。 

接下來我們在App Delegate中宣告陣列和一些Player物件,並把他們分配給PlayerViewController的players屬性。 

在AppDelegate.m中,分別引入(import)Player和PlayerViewController這兩個類,之後新增一個名叫players的可變陣列。 
  1. #import "AppDelegate.h"  
  2. #import "Player.h"  
  3. #import "PlayersViewController.h"  
  4. @implementation AppDelegate {  
  5.     NSMutableArray *players;  
  6. }  
  7. // Rest of file...  

修改didFinishLaunchingWithOptions方法如下: 
  1. - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions  
  2. {  
  3.     players = [NSMutableArray arrayWithCapacity:20];  
  4.     Player *player = [[Player alloc] init];  
  5.     player.name = @"Bill Evans";  
  6.     player.game = @"Tic-Tac-Toe";  
  7.     player.rating = 4;  
  8.     [players addObject:player];  
  9.     player = [[Player alloc] init];  
  10.     player.name = @"Oscar Peterson";  
  11.     player.game = @"Spin the Bottle";  
  12.     player.rating = 5;  
  13.     [players addObject:player];  
  14.     player = [[Player alloc] init];  
  15.     player.name = @"Dave Brubeck";  
  16.     player.game = @"Texas Hold’em Poker";  
  17.     player.rating = 2;  
  18.     [players addObject:player];  
  19.     UITabBarController *tabBarController =  
  20.      (UITabBarController *)self.window.rootViewController;  
  21.     UINavigationController *navigationController =  
  22.      [[tabBarController viewControllers] objectAtIndex:0];  
  23.     PlayersViewController *playersViewController =  
  24.      [[navigationController viewControllers] objectAtIndex:0];  
  25.     playersViewController.players = players;  
  26.     return YES;  
  27. }  

這將會創造一些Player物件並把他們加到陣列中去。之後在加入: 
  1. UITabBarController *tabBarController = (UITabBarController *)  
  2.   self.window.rootViewController;  
  3. UINavigationController *navigationController =  
  4.   [[tabBarController viewControllers] objectAtIndex:0];  
  5. PlayersViewController *playersViewController =  
  6.   [[navigationController viewControllers] objectAtIndex:0];  
  7. playersViewController.players = players;  

咦,這是什麼?目前的情況是:我們希望能夠將players陣列連線到PlayersViewController的players屬性之中以便讓這個VC能夠用做資料來源。但是app delegate根本不瞭解PlayerViewController究竟是什麼,他將需要在storyboard中尋找它。 

這是一個我不是很喜歡storyboard特性,在IB中,你在MainWindow.xib中總是會有一個指向App delegate的選項,在那裡你可以在頂級的ViewController中向Appdelegate設定輸出口,但是在Storyboard中目前這還不可能,目前只能通過程式碼來做這樣的事情。 
  1. UITabBarController *tabBarController = (UITabBarController *)  
  2.   self.window.rootViewController;  

我們知道storyboard的起始場景是Tab Bar Controller,所以我們可以直接到這個場景的第一個子場景來設定資料來源。 

PlayersViewController 在一個NavController的框架之中,所以我們先看一看UINavigationController類: 
  1. UINavigationController *navigationController = [[tabBarController  
  2.   viewControllers] objectAtIndex:0];  

然後詢問它的根試圖控制器,哪一個是我們要找的PlayersViewController: 
  1. PlayersViewController *playersViewController =  
  2.   [[navigationController viewControllers] objectAtIndex:0];  

但是,UIViewController根本就沒有一個rootViewController屬性,所以我們不能把陣列加入進去,他又一個topViewController但是指向最上層的檢視,與我們這裡的意圖沒有關係。 

現在我們有了一個裝在了players物體合集的陣列,我們繼續為PlayersViewController設定資料來源。 

開啟PlayersViewController.m,加入以下資料來源方法: 
  1. - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView  
  2. {  
  3.     return 1;  
  4. }  
  5. - (NSInteger)tableView:(UITableView *)tableView  
  6.   numberOfRowsInSection:(NSInteger)section  
  7. {  
  8.     return [self.players count];  
  9. }  

真正起作用的程式碼在cellForRowAtIndexPath方法裡,預設的模板是如下這樣的