Storyboard 全解析

XCode 4.3.2 新功能 - Storyboard編程

最近開始比較有空在玩 XCode 4.3.2,赫然發現它多了個 Storyboard 的東東。小程序

Storyboard 這個東西通常來講是在作創意發想的時候,用來將本身的想的一些故事情節畫成像是連環漫畫同樣,想不到 Apple 把它用在這裏,真是佩服...設計模式

 

好吧,不廢話,先來講說這個 Storyboard 帶來什麼改變?數組

在這個版本前,咱們在設計畫面的時候都是用 interface builder 產生一個 xib 檔,而後在 code 要出現這個畫面的時候多是用這樣的方式:app

[self.navigationController pushViewController:viewController animated:YES];框架

好,也許你們(包括我)已經習慣這樣的方式了,不過若是畫面一多的話,處處 push 來 pop 去的,誰知道畫面之間是怎麼串呢?可能只有 PG 最清楚吧? 編輯器

如今 Storyboard 解決了這樣的問題,讓你先將故事情節畫出來,像這樣 ide

[attach]3682[/attach]函數

畫面中間那個像是轉接頭的叫作 Segue,先把你要的畫面(View controller)拉到 Storyboard 內以後,比方說要將 A畫面->B畫面,那麼在 A 上面按住 control 鍵,而後拉到 B 畫面,並選擇 performSegueWithIdentifier:sender:,這樣兩個畫面就串起來了,固然你能夠有不少路徑,比方說還能夠有 A->C 或 A->D 之類的。 工具

重點來了,那我在程序代碼裏面要作什麼修改呢? 

只要將本來的 pushViewController:animated: 的地方更改成如下這樣就能夠了

 [self.navigationController performSegueWithIdentifier:@"SegueLevel1" sender:self];

其中 SegueLevel1 是我本身訂的轉接頭名稱,若是你有不少路徑的話,那麼就能夠有多個 Segue。

這樣作法的好處是我能夠很快的把故事情節描述出來,萬一客戶的畫面之間改來改去的,那麼我只要變動個人 Storyboard,只要 Segue identifier 不變,程序代碼天然就不須要修改了。不過少了 xib 一時間卻是有點不習慣呢!

StoryBoard優點

StoryBoard是iOS 5的新特徵,旨在代替歷史悠久的NIB/XIB(其實StoryBoard仍是基於NIB/XIB的,不過開發人員已經無需直接跟NIB打交道了)。目前關於StoryBoard的文檔並很少,蘋果的iOS 5的開發者文檔裏也僅有很少的介紹。因此,本文只是簡單的談談本人對StoryBoard的一些粗淺的理解。(StoryBoard有時也叫作StoryBoarding,我不太注意這種細節,因此兩個詞常常會混用,若是你英語能夠的話,能體會到二者的細微差異)

StoryBoarding機制比之NIB/XIB的的優點何在呢?我的認爲,StoryBoard有如下幾個優勢:

可以減小不少跟View相關的代碼;

可以使View和Controller進一步解耦;

可以優化程序的「頁面流」,使程序的結構更清楚;

要理解這些優勢,咱們先要對NIB有一個基本的認識。一般,NIB是和ViewController相關聯的,不少ViewController都有對應的NIB文件。NIB文件的做用是描述用戶界面以及初始化對象和界面元素對象。其實開發者在NIB裏描述的界面和初始化的對象都可以在代碼中實現;之因此用Interface Builder來繪製界面,是爲了減小那些設置界面屬性的無聊和重複的代碼,讓開發人員可以集中精力作程序的功能。

而StoryBoard的出現,則是進一步增強了這方面的功能;NIB文件是沒有辦法描述從一個ViewController到另外一個ViewController的過渡的。這種過渡只能靠手寫代碼來實現。相信不少人都會常常用到

-presentModalViewController:animated:以及

-pushViewController:animated:

這兩個方法。這種代碼在Storyboarding裏將成爲歷史;取而代之的是Segue。Segue定義了從一個ViewController到另外一個ViewController的過渡。在Storyboard裏,咱們只須要像鏈接界面對象和Action Method那樣把ViewController之間用Segue鏈接起來就能夠了,再也不須要手寫代碼了。即使你像自定義Segue,你也只需寫Segue的實現,而無需編寫調用的代碼,StoryBoard會幫你調用的。這就是上面所說的第一個優勢。

要用好Storyboarding機制,那麼必須嚴格遵照MVC原則。要讓View和Controller充分解耦;而且不一樣的Controller之間也要充分解耦。不然,程序的業務邏輯就會亂成一團,很難理解,維護和除蟲(Debug)。

舉個例子來講:在過去,特別是初學Cocoa Touch開發的時候,不少人都喜歡直接把AppDelegate當ViewController用,直接在AppDelegate和MainMenu.xib之間交互。應該說,這是一個很是很差的習慣。AppDelegate的做用很簡單,就是處理UIApplication的回調,而不該該負責用戶界面的處理。不少iOS教程爲了省事,都直接把AppDelegate當ViewController用,甚至直接舉例在UIWindow上繪製界面。雖然,做爲教程這麼作很簡單明瞭,由於UIWindow也是UIView的子類,可是這卻不是一種優良的實踐。由於由ViewController來負責處理View纔是正確的作法。

近一段時間,蘋果的項目模版常常發生改變,特別是自從Xcode 4發佈以後,程序模版(如,View Based Application)開始鼓勵使用UIWindow的rootViewController屬性來指定第一屏的ViewController,以保證AppDelegate專一於它應該作的事情。而引入StoryBoard以後,AppDelegate已經無論ViewController的事情了;第一屏所使用的ViewController(也就是rootViewController)能夠在StoryBoard中設置。這樣,程序的入口點就能從StoryBoard的「設計圖」上一目瞭然了。這是第二個優勢。

至於第三個優勢,就是StoryBoard的「設計圖」了。StoryBoard可以包含一個程序全部的ViewController以及它們之間的鏈接。所以,StoryBoard甚至能夠做爲程序的「設計圖」來用了。理想狀況下,在程序開發接近尾聲的時候,咱們只需對比StoryBoard的「流程」和最初程序的設計「流程」,就知道程序有沒有「走樣」了。

說完了優勢,咱們來看看從NIB/XIB到StoryBoard的遷移,咱們須要有哪些理解和實踐上的改變呢?

首先,天然是(在作程序開發的時候)ViewController再也不須要NIB/XIB了(雖然在後臺仍是用的NIB)。之前在NIB/XIB上作的鏈接Outlet和Action的操做均可以在StoryBoard上完成了;

第二,孤兒View(獨立於ViewController的View)是不能出如今StoryBoard裏的,View必須經過ViewController來管理(StoryBoard更像是Controller對象的容器,而不是View對象的容器,NIB/XIB能夠做爲View對象的容器); 

第三,ViewController之間的過渡代碼已是歷史了,用StoryBoard能夠直接可視化地鏈接不一樣的ViewController;

第四,UIWindow對象的做用被進一步淡化,甚至能夠這麼說:其實不少程序根本無需用到UIWindow對象。AppDelegate也再也不被鼓勵(也不能)用來作ViewController--你甚至沒法在Interface Builder的StoryBoard圖上找到AppDelegate對象--由於它原本就不該該用來處理界面(View)的。

最後,寫優質的代碼,嚴格遵照MVC設計模式,這樣不只可以讓你用好StoryBoard,也能幫助你理解StoryBoard的原理。

StoryBoard是很是好的鼓勵MVC和代碼解耦的手段,可以讓開發人員寫出更加容易維護的代碼。不過對於初學者來講,確實是個對理解力的小挑戰。不過做爲初學者也不用擔憂,一旦突破了理解障礙,你就會發現StoryBoard也很是好用--就像最初理解NIB/XIB時,Outlet和Action「拉線」來「拉線」去,看起來也很神奇;理解以後,發現原來「拉線」神馬的也沒那麼神祕。 

好了,絮絮不休的囉嗦了這麼多無聊的文字,相信你也看累了。若是你依然對我寫的東西不知所云的話,你能夠稍稍研究一下Xcode 4.3.2的幾個內建模版,而後和使用XIB的模版對比一下,看看蘋果是怎麼用StoryBoard的,可以很好的幫助你理解Storyboarding機制。

(Storyboard)是一個可以節省你不少設計手機App界面時間的新特性,下面,爲了簡明的說明Storyboard的效果,我貼上本教程所完成的Storyboard的截圖:
[轉載][IOS] <wbr>Storyboard全解析-第一部分 
如今,你就能夠清楚的看到這個應用到底是幹些什麼的,也能夠清楚的看到其中的各類關係,這就是Storyboard的強大之處了。若是你要製做一個頁面不少很複雜的App,Storyboard能夠幫助你解決寫不少重複的跳轉方法的麻煩,節省不少時間,以便你可以徹底的專一於核心功能的實現上。
開始
首先啓動Xcode,新建一個工程,咱們在這裏使用Single View App Template,這個模板會提供一個類和一個Storyboard,免去咱們本身建立的麻煩。
[轉載][IOS] <wbr>Storyboard全解析-第一部分 
建立完成以後,Xcode的界面大概是這樣的:
[轉載][IOS] <wbr>Storyboard全解析-第一部分 
這個新的工程由兩個類:AppDelegate和ViewController以及一個Storyboard組成(若是你選擇了兩個設備會有兩個Storyboard),注意這個項目沒有xib文件,讓咱們首先看看Storyboard是什麼樣的,雙擊Storyboard打開他:
[轉載][IOS] <wbr>Storyboard全解析-第一部分 
Storyboard的樣子和工做方式都和Interface Builder(如下簡稱爲IB)像極了,你能夠從左下方的控件庫中拖動控件到你的View之中而且組織他們的排放順序,惟一不一樣的地方就是,Storyboard不止是包含一個視圖控件,而是全部的視圖控件以及他們之間的關係。

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

經過嘗試添加一些控件,你能夠感覺一下Storyboard的工做方式。
[轉載][IOS] <wbr>Storyboard全解析-第一部分 
這個是數據顯示器,顯示全部場景及其控件的結構。
[轉載][IOS] <wbr>Storyboard全解析-第一部分 
在IB中,這個位置顯示的是你的NIB文件中的文件,而在Storyboard中這裏顯示的是ViewController,目前這裏只有一個ViewController,咱們接下來可能會增長一些。

這是一個文檔管理器的縮小版,叫作dock。
[轉載][IOS] <wbr>Storyboard全解析-第一部分 
Dock展現場景中第一級的控件,每一個場景至少有一個ViewController和一個FirstReponder,可是也能夠有其餘的控件,Dock還用來簡單的鏈接控件,若是你須要向ViewController傳遞一個關係時,只須要將其按住Ctrl鍵拖到ViewController上就能夠了。

Note:你大概不會太長使用FirstResponder,由於它只是一個代理控件,表明着當前你所使用的控件。

如今運行這個應用,他會向咱們設計的界面同樣。
[轉載][IOS] <wbr>Storyboard全解析-第一部分 
若是你之前製做過NIB型的應用的話,你也許回去尋找MainWindow.xib ,這個文件包括全部的ViewController,Appdelegate等等,可是在Storyboard中這個特性已經被廢止了。
[轉載][IOS] <wbr>Storyboard全解析-第一部分 
那麼,沒有這個文件,應用從那裏起始呢?

讓咱們打開AppDelegate文件,看看那上面是怎麼說的:
Java代碼
  1. #import <UIKit/UIKit.h>   
  2.   
  3. @interface AppDelegate : UIResponder <UIApplicationDelegate>   
  4.   
  5. @property (strong, nonatomic) UIWindow *window;   
  6.   
  7. @end  

若是要使用Storyboard特性,那麼AppDelegate必須繼承自UIResponder類,以前則是繼承自NSObject類的,並且必須有一個不是UIOutlet類的Window屬性聲明才能夠。

若是你再去看AppDelegate的執行文件,裏面大概什麼都沒有,甚至連 application:didFinishLaunchingWithOptions: 也只是返回了一個 YES,而以前,這裏則需聲明一個ViewController而且將他設置成起始頁面,可是如今這些都沒有了。

祕密就在info.plist文件中, 打開Ratings-Info.plist (在 Supporting Files group裏) 你就會看到這些:
[轉載][IOS] <wbr>Storyboard全解析-第一部分 
在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展現在屏幕上,不須要任何編程工做。

在項目總結面板上,你也能夠看到而且編輯這些信息:
[轉載][IOS] <wbr>Storyboard全解析-第一部分 
若是你還想設置nib文件的話,另外有地方去設置的。

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

以前是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就能夠了。
[轉載][IOS] <wbr>Storyboard全解析-第一部分 
新的Tab Bar Controller附帶了兩個View controller,分別做爲Tab的視圖使用,UITabBarController被稱爲包含視圖,由於他包含這其餘一些View,其餘常見的包含視圖還有那vi嘎提鷗鳥 Controller和SplitView Controller。

在iOS 5中,你還能夠本身寫一個自定義的Controller,這在之前是作不到的。

包含關係在Storyboard中用一下這種箭頭表示。
[轉載][IOS] <wbr>Storyboard全解析-第一部分 
拉一個Label控件到第一個子試圖中,命名爲「First Tab」,再在第二個子視圖中添加一個Label,命名爲「Second Tab」。

注意:當屏幕的縮放大於100%時,你沒法在單個場景中添加控件。

選中Tab Bar Controller,進入屬性檢查器,選中「做爲起始場景」,以下圖:
[轉載][IOS] <wbr>Storyboard全解析-第一部分 
如今那個沒有頭的虛虛的小箭頭指向了Tab Bar Controller,說明他是起始場景。
[轉載][IOS] <wbr>Storyboard全解析-第一部分 
這意味着,當你啓動這個應用的時候,UIApplication將會將這個場景做爲應用的主屏幕。

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

如今運行試試吧
[轉載][IOS] <wbr>Storyboard全解析-第一部分 
code專門爲創造這種Tab Bar的應用準備了一個模板,咱們也可使用他,可是本身有能力不用模板本身作一個Tab Bar也是不錯的事。

若是你添加了多於五個子視圖到一個TabBarcontroller的話,並不會創造五個Tab,第四個tab會自動變成More標籤,不錯吧
製做一個表格視圖

目前鏈接到Tab bar Controller的視圖都是普通的View Controller,如今,我要用一個TableViewController來代替其中的一個ViewController。

單擊第一個視圖並刪除,從Library中拖出一個TableViewController。
[轉載][IOS] <wbr>Storyboard全解析-第一部分 
在選中這個TableViewController的前提下,從Library中拖出一個NavController,將會直接附着在上面。
[轉載][IOS] <wbr>Storyboard全解析-第一部分 
固然也能夠調換順序,我徹底沒意見。

因爲NavController和TabBarController同樣也是一個包含控制器視圖,因此他也必須包含另外一個視圖,你能夠看到一樣的箭頭鏈接者這兩個View。
[轉載][IOS] <wbr>Storyboard全解析-第一部分 
請注意全部嵌套在NavController下的View都會有一個Navigation Bar,你沒法移除它,由於他是一個虛擬的Bar。

若是你檢視屬性檢測器,你就會發現全部bar的屬性都在一塊兒:
[轉載][IOS] <wbr>Storyboard全解析-第一部分 
「Inferred」是Storyboard中的默認設置,他意味着繼承的關係,可是你也能夠改變他。可是請注意這些設置都是爲了讓你更好的進行設計和這樣設置的,隨意修改默認設置會帶來不可碰見的後果,施主自重。

如今讓咱們把這個新的場景鏈接到Tab Bar Controller中,按住Ctrl拖動,或者右鍵。
[轉載][IOS] <wbr>Storyboard全解析-第一部分 
當你放手的時候,一個提示框會出現。
[轉載][IOS] <wbr>Storyboard全解析-第一部分 
固然是選第一個了,Relationship – viewControllers ,這將自動建立兩個場景之間的關係。
[轉載][IOS] <wbr>Storyboard全解析-第一部分 
[轉載][IOS] <wbr>Storyboard全解析-第一部分 
直接拖動就能夠改變Tab Item的順序,同時也會改變顯示Tab的順序,放在最左邊的Tab會第一個顯示。

如今運行試試看吧
[轉載][IOS] <wbr>Storyboard全解析-第一部分 
在咱們在這個應用中加入任何實質性的功能以前,咱們先來清理一下Storyboard,你不須要改變TabBarController中的任何內容而只須要改變他的子視圖就能夠了。

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

在NavController中選中Tab Item而且在屬性編輯其中將其修改成Player。
[轉載][IOS] <wbr>Storyboard全解析-第一部分 
將第二個Tab Item命名爲「Gesture」

咱們接下來把自定義的圖片加入到這些item中, 源碼中包含一個名爲「Image」的文件夾,在那裏你能夠找到咱們用到的資源。

接下來,將NavController的title改成Player,也可使用代碼··
[轉載][IOS] <wbr>Storyboard全解析-第一部分 
運行看一看,難以置信吧,你到如今也沒寫一條代碼。
[轉載][IOS] <wbr>Storyboard全解析-第一部分  

原型表格單元

你也許已經注意到了,自從咱們加入了Table View Controller以後,Xcode便會現實下面這樣一條警告。
[轉載][IOS] <wbr>Storyboard全解析-第一部分 
這條警告是:「Unsupported Configuration: Prototype table cells must have reuse identifiers」意思是,原型表格單元必須有一個身份證(意譯啦)

原型單元格是另外一個Storyboard的好特性之一。在以前,若是你想要自定義一個Table Cell,那麼你就不得不用代碼來實現,要麼就要單首創建一個Nib文件來表示單元格內容,如今你也能夠這樣作,不過原型單元格能夠幫你把這一過程大大的簡化,你如今能夠直接在Storyboard設計器中完成這一過程。

Table View如今默認的會帶有一個空白的原型單元格,選中他,在屬性控制器中將他的Style改成subtitle,這樣的話,每一格就會有兩行字。
[轉載][IOS] <wbr>Storyboard全解析-第一部分 
[轉載][IOS] <wbr>Storyboard全解析-第一部分 
將附件設置爲Disclosure Indicator而且將這個原型單元格的Reuse Identifier 設置喂「PlayerCell」,這將會解決Xcode所警告的問題。

試着運行一個,發現什麼都沒變,這並不奇怪,由於咱們尚未給這個表格設置一個數據來源(DataSource),用以顯示。

新建一個文件,使用UIViewContoller模板,命名爲 PlayersViewController ,設置喂UITableViewController的子類,不要勾選創建XIB文件。

回到Storyboard編輯器,選擇Table View Controller,在身份控制器中,把他的類設置爲PlayerViewController,這對於把Storyboard中的場景和你自定義的子類掛鉤是十分重要的。要是不這麼作,你的子類根本沒用。
[轉載][IOS] <wbr>Storyboard全解析-第一部分 
如今起,當你運行這個應用時,table view controller實際上是PlayersViewContoller的一個實例。

在 PlayersViewController.h 中聲明一個MutableArray(可變數組)
Java代碼
  1. #import <UIKit/UIKit.h>   
  2.   
  3. @interface PlayersViewController : UITableViewController   
  4.   
  5. @property (nonatomic, strong) NSMutableArray *players;   
  6.   
  7. @end  

這個數組將會包含咱們的應用的主要數據模型。咱們如今加一些東西到這個數組之中,新建一個使用Obj-c模板的文件,命名爲player,設置喂NSObject的子類,這將會做爲數組的數據容器。

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

編寫Player.m以下:
Java代碼
  1. #import "Player.h"  
  2.   
  3. @implementation Player   
  4.   
  5. @synthesize name;   
  6. @synthesize game;   
  7. @synthesize rating;   
  8.   
  9. @end  

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

接下來咱們在App Delegate中聲明數組和一些Player對象,並把他們分配給PlayerViewController的players屬性。

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

修改didFinishLaunchingWithOptions方法以下:
Java代碼
  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對象並把他們加到數組中去。以後在加入:
Java代碼
  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中目前這還不可能,目前只能經過代碼來作這樣的事情。
Java代碼
  1. UITabBarController *tabBarController = (UITabBarController *)   
  2.   self.window.rootViewController;  

咱們知道storyboard的起始場景是Tab Bar Controller,因此咱們能夠直接到這個場景的第一個子場景來設置數據源。

PlayersViewController 在一個NavController的框架之中,因此咱們先看一看UINavigationController類:
Java代碼
  1. UINavigationController *navigationController = [[tabBarController   
  2.   viewControllers] objectAtIndex:0];  

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

可是,UIViewController根本就沒有一個rootViewController屬性,因此咱們不能把數組加入進去,他又一個topViewController可是指向最上層的視圖,與咱們這裏的意圖沒有關係。

如今咱們有了一個裝在了players物體合集的數組,咱們繼續爲PlayersViewController設置數據源。

打開PlayersViewController.m,加入如下數據源方法:
Java代碼
  1. - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView   
  2. {   
  3.     return 1;   
  4. }   
  5.   
  6. - (NSInteger)tableView:(UITableView *)tableView   
  7.   numberOfRowsInSection:(NSInteger)section   
  8. {   
  9.     return [self.players count];   
  10. }  

真正起做用的代碼在cellForRowAtIndexPath方法裏,默認的模板是以下這樣的:
Java代碼
  1. - (UITableViewCell *)tableView:(UITableView *)tableView   
  2.   cellForRowAtIndexPath:(NSIndexPath *)indexPath   
  3. {   
  4.     static NSString *CellIdentifier = @"Cell";   
  5.   
  6.     UITableViewCell *cell = [tableView   
  7.       dequeueReusableCellWithIdentifier:CellIdentifier];   
  8.     if (cell == nil) {   
  9.         cell = [[UITableViewCell alloc]   
  10.           initWithStyle:UITableViewCellStyleDefault   
  11.           reuseIdentifier:CellIdentifier];   
  12.     }   
  13.   
  14.     // Configure the cell...   
  15.     return cell;   
  16. }  

無疑這就是之前設置一個表格視圖的方法,不過如今已經革新了,把這些代碼修改以下:
Java代碼
  1. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath   
  2. {   
  3.     UITableViewCell *cell = [tableView   
  4.       dequeueReusableCellWithIdentifier:@"PlayerCell"];   
  5.     Player *player = [self.players objectAtIndex:indexPath.row];   
  6.     cell.textLabel.text = player.name;   
  7.     cell.detailTextLabel.text = player.game;   
  8.     return cell;   
  9. }  

這看上去簡單多了,爲了新建單元格,你只需使用以下代碼:
Java代碼
  1. UITableViewCell *cell = [tableView   
  2.   dequeueReusableCellWithIdentifier:@"PlayerCell"];  

若是沒有現存的單元格能夠回收,程序會自動創造一個原型單元格的複製品以後返回給你,你只須要提供你以前在Storyboard編輯視圖中設置的身份證就能夠的,在這裏就是「PlayerCell」,若是不設置這個,這個程序就沒法工做。

因爲這個類對於Player容器目前一無所知,因此咱們須要在文件的開頭加入一個引入來源
Java代碼
  1. #import "Player.h"  

記得要建立synthesize語句哦親
Java代碼
  1. @synthesize players;  

如今運行應用,會看到Table裏有着players容器。
[轉載][IOS] <wbr>Storyboard全解析-第一部分 
請注意:咱們這裏只使用一種單元格原型,若是你須要使用不一樣類型的單元格的話,只須要在storyboard中另外加入一個單元格原型就能夠了,不過不要忘記給他們指派不一樣的身份證。

設計自定義的原型單元格

對於不少應用來講,使用默認的單元格風格就OK了,可是我恰恰要在每個單元格的右邊加上一個一個圖片來表示選手的評級,可是添加圖片對於默認類型的單元格來講並不支持,咱們須要自定義一個設計。

讓咱們轉回MainStoryboard.storyboard,選中table view中的prototype cell,把它的Style attribute改成Custom,全部默認的標籤都會消失。

首先把單元格變得更高一些,你能夠直接拉它,也能夠在大小控制器中修改數字,我在這裏使用55點的高度。

從 Objects Library中拖出兩個標籤物體,按照以前的樣式安插到單元格里,記得設置label的Highlighted顏色爲白色,那樣的話當單元格被選中的時候會看起來更好看一些。

以後添加一個Image View對象,將它放置在單元格的右邊,設置他的寬度爲81點,高度並不重要,在屬性檢查器中設置模式爲置中。

我把標籤設置爲210點長以確保他不會和ImageView重合,最後總體的設計會看起來象下面這樣:
[轉載][IOS] <wbr>Storyboard全解析-第一部分 
因爲這是一個自定義的單元格,因此咱們不可以使用UITableView默認的textLabel和detailLabel來設置數據,這些屬性也再也不指向咱們的單元格了,咱們使用標籤(tags)來指定標籤。

將Name標籤的tag設置爲100,Game的設置喂101,image的設置喂102,在屬性檢查器裏設置哦親。

以後打開 PlayersViewController.m ,在PlayersViewcontroller中將cellForRowatIndexPath修改成:
Java代碼
  1. - (UITableViewCell *)tableView:(UITableView *)tableView   
  2.   cellForRowAtIndexPath:(NSIndexPath *)indexPath   
  3. {   
  4.     UITableViewCell *cell = [tableView   
  5.       dequeueReusableCellWithIdentifier:@"PlayerCell"];   
  6.     Player *player = [self.players objectAtIndex:indexPath.row];   
  7.     UILabel *nameLabel = (UILabel *)[cell viewWithTag:100];   
  8.     nameLabel.text = player.name;   
  9.     UILabel *gameLabel = (UILabel *)[cell viewWithTag:101];   
  10.     gameLabel.text = player.name;   
  11.     UIImageView * ratingImageView = (UIImageView *)   
  12.       [cell viewWithTag:102];   
  13.     ratingImageView.image = [self imageForRating:player.rating];   
  14.     return cell;   
  15. }  

這裏是用了一個新的方法,叫作ImageRating,在 cellForRowAtIndexPath方法以前加入這個方法:
Java代碼
  1. - (UIImage *)imageForRating:(int)rating   
  2. {   
  3.     switch (rating)   
  4.     {   
  5.         case 1return [UIImage imageNamed:@"1StarSmall.png"];   
  6.         case 2return [UIImage imageNamed:@"2StarsSmall.png"];   
  7.         case 3return [UIImage imageNamed:@"3StarsSmall.png"];   
  8.         case 4return [UIImage imageNamed:@"4StarsSmall.png"];   
  9.         case 5return [UIImage imageNamed:@"5StarsSmall.png"];   
  10.     }   
  11.     return nil;   
  12. }  

這就完成了,運行看看:
[轉載][IOS] <wbr>Storyboard全解析-第一部分 
這和咱們想象的結果並非很符合,咱們修改了原型單元格的屬性和高度,可是table view卻沒有考慮進去,有兩種方法能夠修復它,咱們能夠改變table view的行高或者加入 heightForRowAtIndexPath 方法來修改,地一種方法更簡單,咱們就用他。

注意:在一下兩種狀況下,你應該使用 heightForRowAtIndexPath 方法:一是,你不能預先知道你的單元格的高度,二是不一樣的單元格會有不一樣的高度。

回到MainStoryboard.storyboard,在大小檢查器中將高度設置爲55:
[轉載][IOS] <wbr>Storyboard全解析-第一部分 
經過這種方式的話,若是以前你是使用拖動而不是鍵入數值的方式改變高度的屬性的話,則table view的數值也會自動改變。

如今運行看看,好多了吧

爲原型單元格設置子類

咱們的表格視圖已經至關像模像樣了,可是我並非很喜歡使用tag來訪問label,要是咱們可以把這些lable鏈接到輸出口,以後在迴應屬性中使用他們,該多好,並且不出所料,咱們能夠這樣作。

使用 Objective-C class模板新建一個文件,命名爲PlayerCell,繼承UITableViewCell。

修改PlayerCell.h
Java代碼
  1. @interface PlayerCell : UITableViewCell   
  2.   
  3. @property (nonatomic, strong) IBOutlet UILabel *nameLabel;   
  4. @property (nonatomic, strong) IBOutlet UILabel *gameLabel;   
  5. @property (nonatomic, strong) IBOutlet UIImageView   
  6.   *ratingImageView;   
  7.   
  8. @end  

修改PlayerCell.m
Java代碼
  1. #import "PlayerCell.h"  
  2.   
  3. @implementation PlayerCell   
  4.   
  5. @synthesize nameLabel;   
  6. @synthesize gameLabel;   
  7. @synthesize ratingImageView;   
  8.   
  9. @end  

這個類自己並不其很大的做用,只是爲nameLabel、gameLabel和ratingImageView聲明瞭屬性。

回到MainStoryboard.storyboard選中原型單元格,將他的class屬性修改成「PlayerCell」,如今當你向table view請求dequeueReusableCellWithIdentifier,他會返回一個PlayerCell實例而不是一個普通的UITableViewCell實例。

請注意我將這個類和reuse Indetifier的名字命名的同樣,只是營衛我喜歡這樣哦親,這兩個之間其實沒啥關係。

如今你能夠將標籤和image view鏈接到輸出口去了,選中或者將他從連接檢查器拖動到table view cell。
[轉載][IOS] <wbr>Storyboard全解析-第一部分 
請注意:要把這個control鏈接到table view cell而不是view controller哦親,別選錯了。

如今咱們把一切都連接好了,只須要加入數據源的代碼就能夠了。
Java代碼
  1. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath   
  2. {   
  3.     PlayerCell *cell = (PlayerCell *)[tableView   
  4.      dequeueReusableCellWithIdentifier:@"PlayerCell"];   
  5.     Player *player = [self.players objectAtIndex:indexPath.row];   
  6.     cell.nameLabel.text = player.name;   
  7.     cell.gameLabel.text = player.game;   
  8.     cell.ratingImageView.image = [self   
  9.       imageForRating:player.rating];   
  10.     return cell;   
  11. }  

咱們如今將接收到 dequeueReusableCellWithIdentifier 的控件指派到PlayerCell,只須要簡單的使用已經連接labels和image view到設置好的屬性上就能夠了,這會讓這個設計看上去更加好控制,更加簡明。

固然,在PlayerCell前要引入資源:
Java代碼
  1. #import "PlayerCell.h"  

試着運行,你會發現其實什麼都沒有變化,但是咱們都知道,內部已經有了變化。

在這相同的場景下面,咱們但是在使用子類呢。

這裏還有一些設計小竅門:第一點:必定要設置標籤被選中時的顏色。
[轉載][IOS] <wbr>Storyboard全解析-第一部分 
第二點,確保你加入單元格的字符大小是能夠變化的,這樣,當單元格大小變化時,他的內容的大小也會跟着變化,好比說:

在PlayersViewController.m中加入以下方法:
Java代碼
  1. - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath   
  2. {   
  3.     if (editingStyle == UITableViewCellEditingStyleDelete)   
  4.     {   
  5.         [self.players removeObjectAtIndex:indexPath.row];   
  6.         [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];   
  7.     }   
  8. }  

這個方法加入好了以後,用手指輕掃一行單元格,會出現一個刪除鍵,試試看
[轉載][IOS] <wbr>Storyboard全解析-第一部分 
Delete按鈕出如今右邊,遮住了一部分評級圖片,怎麼解決呢?

打開MainStoryBoard.storyboard,選中table view cell中的image view,在大小檢查器中修改Autosizing屬性,是它可以跟隨上級view的邊緣。
[轉載][IOS] <wbr>Storyboard全解析-第一部分 
爲labels設置一樣的屬性。
[轉載][IOS] <wbr>Storyboard全解析-第一部分 
加入了這些變更以後,刪除按鈕如咱們意料的出現了:
[轉載][IOS] <wbr>Storyboard全解析-第一部分 
其實,最好的作法是讓這些星星在出現delete按鈕的時候消失,不過這只是一個練習,不要太較真哦親
 
 
 
若是你想了解更多Storyboard的特性,那麼你就來對了地方,下面咱們就來接着上次的內容詳細講解Storyboard的使用方法。
    在上一篇《Storyboard全解析-第一部分》中,咱們介紹瞭如何使用storyboard來製做多種場景和如何將這些場景連接起來,咱們還學習瞭如何自定義一個表格視圖。
    接下來這部分,也是最後一部分,咱們將講解聯線(segue),靜態單元格等內容,咱們還將加入一個選手詳細內容頁面,和一個遊戲選擇頁面。 

Segues的介紹
    如今,讓咱們建立一個場景使用戶能夠本身增長新的選手進入列表。
    在Players界面中拖入一個Bar Button,放置在導航欄的右側,在屬性監視器中將他的Identifier改成「add」,這樣他就會顯示一個加號的按鈕,當用戶點擊這個按鈕時,他就會彈出一個新的場景讓用戶對新的內容進行編輯或添加。

    在編輯器中拖入一個新的Table View Controller,放置在Players場景的右邊,而後按住ctrl,拉動加號鍵到新的場景中,這樣,這個場景就會自動和這個按鈕創建聯繫,從而自動納入Navigation View Controller中。
[轉載][IOS] <wbr>Storyboard全解析-第二部分 
放開鼠標以後,會出現以下選項:
[轉載][IOS] <wbr>Storyboard全解析-第二部分 
選中Modal,你能夠注意到出現了一種新的箭頭形式:
[轉載][IOS] <wbr>Storyboard全解析-第二部分 
這種連接形式被官方稱爲segue(pronounce: seg-way),我叫它聯線,(實際上是轉換的意思)這種形式的聯線是表示從一種場景轉換到另一種場景中,以前咱們使用的鏈接都是描述一種場景包含另外一種場景的。而對於聯線來講,它會改變屏幕中顯示的內容,並且必須由交互動做觸發:如輕點,或其餘手勢。

聯線真正了不得的地方在於:你再也不須要寫任何代碼來轉入一個新的場景,也不用在將你的按鈕和IBAction鏈接到一塊兒,咱們剛纔作的,直接將按鈕和場景連接起來,就可以完成這項工做。

運行這個app,按下 + 鍵,會發現出現了一個新的列表。
[轉載][IOS] <wbr>Storyboard全解析-第二部分 
這種叫作 「modal」 segue(模態轉換),新的場景徹底蓋住了舊的那個。用戶沒法再與上一個場景交互,除非他們先關閉這個場景,過一會咱們會討論 push segue,這種segue會把場景推入導航棧。

新的場景如今尚未什麼用,你甚至不能把他關閉呢。

聯線只可以把你送到新的場景,你要是想回來,就得使用delegate pattern,代理模式。咱們必須首先給這個新的場景設置一個獨有的類,新建一個繼承UITableViewController的類,命爲PlayerDetailsViewController。

爲了把它和storyboard相連,回到MainStoryBoard,選擇新建的那個Table View Contrller,將他的類設置喂PlayerDetailViewController,千萬不要忘記這一步,這很重要。

作完這一步以後,把新場景的標題改成「Add Player」,分別加入「Done」和「Cancel」兩個導航欄按鈕。
[轉載][IOS] <wbr>Storyboard全解析-第二部分 
修改PlayerDetailsViewController.h 以下:
Java代碼
  1. @class PlayerDetailsViewController;   
  2.   
  3. @protocol PlayerDetailsViewControllerDelegate <NSObject>   
  4. - (void)playerDetailsViewControllerDidCancel:   
  5.   (PlayerDetailsViewController *)controller;   
  6. - (void)playerDetailsViewControllerDidSave:   
  7.   (PlayerDetailsViewController *)controller;   
  8. @end  
  9.   
  10. @interface PlayerDetailsViewController : UITableViewController   
  11.   
  12. @property (nonatomic, weak) id <PlayerDetailsViewControllerDelegate> delegate;   
  13.   
  14. - (IBAction)cancel:(id)sender;   
  15. - (IBAction)done:(id)sender;   
  16.   
  17. @end  
 

這會聲明一個新的代理機制,當用戶點擊Cancel或者done按鈕時,咱們將用它來交互Add Player場景和主場景通信。

回到故事版編輯器,將Cancel和Done按鈕分別與動做方法鏈接,一種方式是,按住Ctrl拖動到ViewController上,以後選擇正確的動做。
[轉載][IOS] <wbr>Storyboard全解析-第二部分 
在 PlayerDetailsViewController.m,加入以下代碼:
Java代碼
  1. - (IBAction)cancel:(id)sender   
  2. {   
  3.     [self.delegate playerDetailsViewControllerDidCancel:self];   
  4. }   
  5. - (IBAction)done:(id)sender   
  6. {   
  7.     [self.delegate playerDetailsViewControllerDidSave:self];   
  8. }  
 

這是兩個導航欄按鈕要使用的方法,如今只須要讓代理知道咱們剛纔加入了代碼,而真正關閉場景只是代理的事情。

通常來講必定要爲代理制定一個對象參數,這樣他才知道向那裏發送信息。

不要忘記加入Synthesize語句。
Java代碼
  1. @synthesize delegate;  
 

如今咱們已經爲PlayerDetailsViewController設置了一個代理協議,咱們須要將這個協議的實現方法(implement)寫在什麼地方,很明顯應該寫在PlayerViewController由於這個vc表明了Add Player場景。在PlayersViewController.h中加入以下代碼:
Java代碼
  1. #import "PlayerDetailsViewController.h"  
  2.   
  3. @interface PlayersViewController : UITableViewController <PlayerDetailsViewControllerDelegate>  
 

並在PlayersViewController.m的結尾加入:
Java代碼
  1. #pragma mark - PlayerDetailsViewControllerDelegate   
  2.   
  3. - (void)playerDetailsViewControllerDidCancel:   
  4.   (PlayerDetailsViewController *)controller   
  5. {   
  6.     [self dismissViewControllerAnimated:YES completion:nil];   
  7. }   
  8.   
  9. - (void)playerDetailsViewControllerDidSave:   
  10.   (PlayerDetailsViewController *)controller   
  11. {   
  12.     [self dismissViewControllerAnimated:YES completion:nil];   
  13. }  
 

目前這個代理方法只可以跳轉到這個新的場景中,接下來咱們來讓他作一些更爲強大的事情。

iOS 5 SDK中新添加的dismissViewControllerAnimated:completion: 方法能夠被用來關閉一個場景。

最後還有一件事情須要作,就是Players場景須要告訴PlayerDetailsVC他的代理在哪裏,聽上去這種工做在故事版編輯其中一拖就好了,實際上,你得使用代碼才能完成。

將如下方法加入到 PlayersViewController 中
Java代碼
  1. - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender   
  2. {   
  3.     if ([segue.identifier isEqualToString:@"AddPlayer"])   
  4.     {   
  5.         UINavigationController *navigationController =   
  6.           segue.destinationViewController;   
  7.         PlayerDetailsViewController   
  8.           *playerDetailsViewController =   
  9.             [[navigationController viewControllers]   
  10.               objectAtIndex:0];   
  11.         playerDetailsViewController.delegate = self;   
  12.     }   
  13. }  
 

當使用Segue的時候,就必須加入這個名叫 prepareForSegue 的方法,這個新的ViewController在被加載的時候仍是不可見的,咱們能夠利用這個機會來向他發送數據。

請注意,這個segue的最終目標是Navigation Controller,由於這個是咱們連接在導航欄上的按鈕,爲了獲取PlayerDetailsViewController實例,咱們必須經過NavController的屬性來獲取。

試着運行一下這個應用,單擊 + 鍵,而後試着關閉Add Player場景,仍然無論用。

這是由於咱們沒有給Segue指定一個identifier,而parepareForSegu須要檢查AddPlayer的身份證,這是必須的,由於你有可能會同時使用多個聯線。

爲了解決這個問題,進入Storyboard的編輯器,點擊Players場景和NavgationViewController場景之間的聯線,你會注意到與這個連線相關的按鈕會自動亮起來。

在屬性監視器中,將Identifier設置喂「AddPlayer」
[轉載][IOS] <wbr>Storyboard全解析-第二部分 
若是這是你再次運行這個應用,點擊「Cancel」或者「Done」按鈕,這個場景就會自動關閉而且返回到上一級場景。

注意:從modal場景調用dismissViewControllerAnimated:completion方法是咱們在這裏使用的,可是這並不意味着你必須這樣作。可是,若是你不是代理來完成這個關閉窗口的工做的話,惟一須要注意的是,若是你以前使用了[self.parentViewController dismissModalViewControllerAnimated:YES] 語句來關閉窗口的話,那麼這個語句就不會正常工做了。

順便說一下,屬性檢查器中有一個Transition的選項,在這裏你能夠選擇場景轉換是的動畫效果。
[轉載][IOS] <wbr>Storyboard全解析-第二部分 
試着運行一下,看看那種動畫你最喜歡吧,但事情不要改變Style這個選項,若是你改變了,這個app可能會crash哦。

咱們接下來在這個教程中還會用到幾回代理方法,下面咱們來列一下爲了完成一個連線,你須要作的幾件事情。

  • 首先,從起始的控件作一條聯線到目標場景。
  • 將這個聯線制定一個獨特的Identifier。
  • 爲目標場景製做一個代理方法。
  • 在Cancel和Done按鈕,以及全部其餘你須要和原始場景交流的地方調用代理方法。
  • 在原始場景執行代理方法,這將會在用戶按下按鈕後關閉場景。
  • 在原始場景執行prepareForSegue方法。


咱們在這裏必須使用代理,是由於根本沒有反向聯線這種東西,當sugue被啓動以後,他將會創造出一個目標場景的新實例。你固然能夠作一個從目標場景回到原始場景的聯線,可是結果可能與你但願的截然不同。

距離來講吧,若是你作一條從cancel按鈕回到原始場景的連線的話,他並不會關閉當前場景並返回原始場景,而是會建立一個原始場景的新實例,這種狀況會不停循環,知道把內存耗盡爲止。

因此請記住:segue只用於打開新的場景。

靜態單元格

當咱們所有完成以後,Add Player場景會看上去象下面的同樣:
[轉載][IOS] <wbr>Storyboard全解析-第二部分 
這是一種分組表格視圖,可是不一樣的是,咱們並不須要爲這個表哥建立一個數據源,咱們能夠在故事版編輯器中直接設計這個視圖,而不須要重寫cellForRowAtIndex方法,使得咱們能夠這樣作的祕訣就是靜態單元格。

選中Add Player場景,以後在屬性檢查器中,將Content屬性改成StaticCell,將Style to Grouped屬性修改成2。
[轉載][IOS] <wbr>Storyboard全解析-第二部分 
當你修改Section屬性時,編輯器會複製一個現有的組。你也能夠本身選中一個組後選擇Duplicate。

咱們的這個場景每一個組只須要用一個行,因此選中上面的那個行以後刪除。

選中頂行,修改Header的值爲:「Player Name」.
[轉載][IOS] <wbr>Storyboard全解析-第二部分 
拖一個新的Text Field進入這個組的單元格里,把它的邊界刪除掉,使用System 17字體,取消Adjust to Fit選項。

咱們如今在PlayerDetailsViewController中使用Assistant Editor這個Xcode 4.x的新特性來建立一個輸出口給這個Text Field,在工具欄的按鈕中打開Assistant Editor,那玩意看起來像個外星人,我指的是按鈕。

選中text field,按住Ctrl,將他拖到打開的文件之中。
[轉載][IOS] <wbr>Storyboard全解析-第二部分 
放開鼠標,會出現一個選單。
[轉載][IOS] <wbr>Storyboard全解析-第二部分 
將這個新的書出口命名爲nameTextField,在你肯定連接以後,Xcode會自動建立下列代碼:
Java代碼
  1. @property (strong, nonatomic) IBOutlet UITextField *nameTextField;  

他還會自動建立Synthesize語句,並同時在viewDidLoad文件中建立方法。

永遠別在動態表格中使用這種拖來拖去的方法,可是對於靜態單元格來講就OK,對於每一個靜態單元格來講都必須建立一個新的實例。

將第二個組的靜態單元格的Style設置爲Right Detail,這將會建立一個標準的單元格,把左側的label的內容修改成Game,設置一個Disclosure Indicator,爲右側Detail的label設置一個輸出口。

最終的設計完成後是這樣的:
[轉載][IOS] <wbr>Storyboard全解析-第二部分 
當你使用靜態單元格的時候,你的Table View Controller就不須要一個數據源了,可是由於咱們使用了Xcode的模板來創造PlayerDetailsViewController這個類,他裏面仍然有一些默認的數據源設置代碼,讓咱們來刪除之。在如下這個標誌
Java代碼
  1. #pragma mark - Table view data source  

和這個標誌之間的代碼所有刪除。
Java代碼
  1. #pragma mark - Table view delegate  

如今運行這個App,效果不錯吧,請注意咱們不但一行代碼也沒寫,還刪除了好些。

可是咱們並不可以徹底避免寫任何代碼,你可能已經注意到了,在文本框和單元格周圍有一些空間,用戶在完成編輯以後單擊這些區域並不會結束鍵盤什麼的,怎麼避免這個問題呢?用下面的代碼代替tableView:didSelectRowatIndex方法。
Java代碼
  1. - (void)tableView:(UITableView *)tableView   
  2.   didSelectRowAtIndexPath:(NSIndexPath *)indexPath   
  3. {   
  4.     if (indexPath.section == 0)   
  5.         [self.nameTextField becomeFirstResponder];   
  6. }  

這些代碼就是說:若是用戶點擊第一個單元格後咱們激活text field控件,這雖然是細節,可是細節決定成敗。

同時你也須要在屬性檢查器的Selection Style選項改成None。

OK,咱們的設計所有完成了。

增長一個選手吧

如今咱們暫時先忽略Game這一行,先讓用戶可以編輯選手的狀況以後再說。

當用戶單擊Cancel鍵的時候,無論做出什麼修改都會被棄置,場景也會關閉並返回上一級菜單。這一塊的程序咱們已經作好了,也就是咱們剛纔作得一個代理方法,它接收到did cancel這個方法以後就會關閉這個視圖。

可是當用戶單擊「Done」這個按鈕時,咱們應該建立一個新的選手項目而後加入他的屬性,以後咱們還須要通知代理器咱們新增了一個選手,以便它可以更新上一級菜單。

在 PlayerDetailsViewController.m,把完成的方法改爲:
Java代碼
  1. - (IBAction)done:(id)sender   
  2. {   
  3.     Player *player = [[Player alloc] init];   
  4.     player.name = self.nameTextField.text;   
  5.     player.game = @"Chess";   
  6.     player.rating = 1;   
  7.     [self.delegate playerDetailsViewController:self   
  8.      didAddPlayer:player];   
  9. }  

這須要咱們引進Player的頭文件:
Java代碼
  1. #import "Player.h"  

這個完成方法會建立一個新的Player實例,並把它發送給代理器,因爲目前代理器尚未這個方法,因此咱們須要在PlayerDetailsViewController的頭文件中修改以下代碼:
Java代碼
  1. @class Player;   
  2.   
  3. @protocol PlayerDetailsViewControllerDelegate <NSObject>   
  4. - (void)playerDetailsViewControllerDidCancel:   
  5.   (PlayerDetailsViewController *)controller;   
  6. - (void)playerDetailsViewController:   
  7.   (PlayerDetailsViewController *)controller   
  8.   didAddPlayer:(Player *)player;   
  9. @end  

這個「Did Save」的方法的聲明沒有了,咱們加入一個「didAddPlayer」方法。

下面咱們須要在執行文件中加入執行的方法,打開PlayersViewController.m,加入:
Java代碼
  1. - (void)playerDetailsViewController:   
  2.   (PlayerDetailsViewController *)controller   
  3.   didAddPlayer:(Player *)player   
  4. {   
  5.     [self.players addObject:player];   
  6.     NSIndexPath *indexPath =   
  7.      [NSIndexPath indexPathForRow:[self.players count] - 1  
  8.        inSection:0];   
  9.     [self.tableView insertRowsAtIndexPaths:   
  10.       [NSArray arrayWithObject:indexPath]   
  11.        withRowAnimation:UITableViewRowAnimationAutomatic];   
  12.     [self dismissViewControllerAnimated:YES completion:nil];   
  13. }  

第一個語句向players的數組中加入新的Player對象,以後他會通知表格視圖:一個新的行已經被建立,這是由於table view和他的數據源必須一直是同步的才行,咱們其實也可使用[self.tableView reloadData]這個語句,可是從新建立一個單元格會有隨之而來的動畫,看起來更好看一些。UITableViewRowAnimationAutomatic是一個iOS 5的新特性,使各行自動選擇合適的動畫效果出現,很是好用。

如今試試看,你應該可使用按鈕加入新行到表視圖中了。

若是你已經開始擔憂storyboard的性能了,那麼不用擔憂。就算是將全部的場景都一塊載入的話,也不會消耗多少資源的。storyboard不會一會兒加載全部的ViewController,而是會加載起始場景,在這裏是Tab View,再從起始場景加載其餘與起始場景相關的場景。

可是其餘場景知道聯線到他們以前是不會被加載的。而這些場景在你返回以後都會卸載,因此只有當前場景會在內存中,就像你以前在用分開的nib文件同樣的。

咱們經過實驗來看一看。在PlayerDetailsViewController.m中加入下面的方法:
Java代碼
  1. - (id)initWithCoder:(NSCoder *)aDecoder   
  2. {   
  3.     if ((self = [super initWithCoder:aDecoder]))   
  4.     {   
  5.         NSLog(@"init PlayerDetailsViewController");   
  6.     }   
  7.     return self;   
  8. }   
  9. - (void)dealloc   
  10. {   
  11.     NSLog(@"dealloc PlayerDetailsViewController");   
  12. }  

咱們重寫了initWithCoder和dealloc方法,使得debug控制檯輸出一個很長的信息。這時候運行這個app,你會發現除非按下segue的按鈕,不然新的場景不會被初始化,放心了吧。

還有一件關於靜態單元格的事情須要注意,那就是他們只可以在UITableViewController的子類下使用,若是他的父類不是UITableViewController,Xcode會提示下面的錯誤:

「Illegal Configuration: Static table views are only valid when embedded in UITableViewController instances」.

原型單元格,雖然能夠在普通的View Controller中使用,可是不可以在Interface Builder中使用,

不多會出現有人會想要在一個表中用靜態單元格和原型單元格混合起來,目前iOS SDK還不能很好的支持這種方法。

遊戲選擇器場景

在Add Player場景中單擊Game的單元格會打開一個新的場景,讓你可以從一個列表中選擇一個遊戲,這意味着咱們須要加入一個新的表格視圖,不過不一樣的是,咱們此次會使用push到Navigation的棧之中,而不是直接跳轉。

拖拉一個新的TableViewController到編輯器中,在Add Player場景中選擇一個單元格按住ctrl鍵拉到新的場景中,建立一個連線,選擇Push,以後把新segue的identifier命名爲「PickGame」。

雙擊導航欄,修改標題爲「Choose Game」,修改原型單元格的Style爲Basic,修改他的Identifier爲「GameCell」,咱們的試圖設計就到這裏。
[轉載][IOS] <wbr>Storyboard全解析-第二部分 
新建一個UITableViewController的子類,命名爲GamePickerViewController,在storyboard中也要設置好哦。

首先咱們給這個新的場景一些數據來顯示,在GamePickerViewController.h中加入下列變量:
Java代碼
  1. @interface GamePickerViewController : UITableViewController {   
  2.     NSArray * games;   
  3. }  

以後轉到GamePickerViewController.m,在viewDidLoad方法中加入數組的內容。
Java代碼
  1. - (void)viewDidLoad   
  2. {   
  3.     [super viewDidLoad];   
  4.     games = [NSArray arrayWithObjects:   
  5.              @"Angry Birds",   
  6.              @"Chess",   
  7.              @"Russian Roulette",   
  8.              @"Spin the Bottle",   
  9.              @"Texas Hold’em Poker",   
  10.              @"Tic-Tac-Toe",   
  11.              nil];   
  12. }  

因爲在viewDidLoad方法中加載了數組,因此須要在viewDidUnload中卸載之。
Java代碼
  1. - (void)viewDidUnload   
  2. {   
  3.     [super viewDidUnload];   
  4.     games = nil;   
  5. }  

將模板中的數據源方法修改成以下代碼:
Java代碼
  1. - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView   
  2. {   
  3.     return 1;   
  4. }   
  5. - (NSInteger)tableView:(UITableView *)tableView   
  6.   numberOfRowsInSection:(NSInteger)section   
  7. {   
  8.     return [games count];   
  9. }   
  10.   
  11. - (UITableViewCell *)tableView:(UITableView *)tableView   
  12.   cellForRowAtIndexPath:(NSIndexPath *)indexPath   
  13. {   
  14.     UITableViewCell *cell = [tableView   
  15.      dequeueReusableCellWithIdentifier:@"GameCell"];   
  16.     cell.textLabel.text = [games objectAtIndex:indexPath.row];   
  17.     return cell;   
  18. }  

這樣咱們就完成了家在數據源的方法,這時候運行這個app,以後在Add Player場景中單擊Game欄,就會轉入這個視圖了,但這時候單擊這裏的單元格並不會有什麼做用。
[轉載][IOS] <wbr>Storyboard全解析-第二部分 
這時候,因爲咱們使用push方式將這個場景推動了Navigation的棧中,因此這時候咱們單擊返回按鈕就會自動返回到上一級界面。不錯吧!

固然了,若是這個場景不輸送任何數據回到上一級場景的話,那他就什麼用也沒有了,因此咱們要創造一個新的代理器來完成這項任務。在GamePickerViewController.h中加入:
Java代碼
  1. @class GamePickerViewController;   
  2.   
  3. @protocol GamePickerViewControllerDelegate <NSObject>   
  4. - (void)gamePickerViewController:   
  5.   (GamePickerViewController *)controller   
  6.   didSelectGame:(NSString *)game;   
  7. @end  
  8.   
  9. @interface GamePickerViewController : UITableViewController   
  10.   
  11. @property (nonatomic, weak) id <GamePickerViewControllerDelegate> delegate;   
  12. @property (nonatomic, strong) NSString *game;   
  13.   
  14. @end  

咱們加入了一個代理方法,其中只有一個方法和一個用於乘放目前選擇的遊戲的名字的屬性。

如今,咱們修改GamePickerViewController.m的開頭:
Java代碼
  1. @implementation GamePickerViewController   
  2. {   
  3.     NSArray *games;   
  4.     NSUInteger selectedIndex;   
  5. }   
  6. @synthesize delegate;   
  7. @synthesize game;  

這些代碼新建了一個數組,一個選中項目的整數,而且synthesize了這些項目。

在viewDidLoad中加入以下代碼:
Java代碼
  1. selectedIndex = [games indexOfObject:self.game];  

選中的遊戲名字會設置在self.game中,這裏咱們設置咱們在表格中到底選中了哪一個遊戲。在這裏,在場景加載以前必須首先填充self.game,因爲咱們在viewDidLoad以前設置了prepareForSegue這個方法,因此咱們如今這麼作沒問題。

修改cellForRowAtIndexPath方法:
Java代碼
  1. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath   
  2. {   
  3.     UITableViewCell *cell = [tableView   
  4.      dequeueReusableCellWithIdentifier:@"GameCell"];   
  5.     cell.textLabel.text = [games objectAtIndex:indexPath.row];   
  6.     if (indexPath.row == selectedIndex)   
  7.         cell.accessoryType =   
  8.           UITableViewCellAccessoryCheckmark;   
  9.     else  
  10.         cell.accessoryType = UITableViewCellAccessoryNone;   
  11.     return cell;   
  12. }  

這個方法會在選中的項目的右邊加上一個選中的對勾。

將 didSelectRowAtIndexPath 修改成:
Java代碼
  1. - (void)tableView:(UITableView *)tableView   
  2.   didSelectRowAtIndexPath:(NSIndexPath *)indexPath   
  3. {   
  4.     [tableView deselectRowAtIndexPath:indexPath animated:YES];   
  5.     if (selectedIndex != NSNotFound)   
  6.     {   
  7.         UITableViewCell *cell = [tableView   
  8.           cellForRowAtIndexPath:[NSIndexPath   
  9.             indexPathForRow:selectedIndex inSection:0]];   
  10.         cell.accessoryType = UITableViewCellAccessoryNone;   
  11.     }   
  12.     selectedIndex = indexPath.row;   
  13.     UITableViewCell *cell =   
  14.      [tableView cellForRowAtIndexPath:indexPath];   
  15.     cell.accessoryType = UITableViewCellAccessoryCheckmark;   
  16.     NSString *theGame = [games objectAtIndex:indexPath.row];   
  17.     [self.delegate gamePickerViewController:self   
  18.      didSelectGame:theGame];   
  19. }  

首先咱們取消以前點擊的那一行的選中狀態,這將把它的藍色變會正常的白色,以後將對勾刪除掉,以後將對勾放置在剛剛選中的那一行上,最後,咱們把選中的那一行返回給代理。

如今運行這個app測試一下效果,單擊一個game的名字,將會出現一個對勾,單擊另外一個行,對勾的位置就會改變,可是返回上一級菜單以後發現咱們的修改沒有保存下來,爲何?由於咱們尚未將代理真正的連接起來。

在 PlayerDetailsViewController.h 中,引入
Java代碼
  1. #import "GamePickerViewController.h"  

以後在 @interface 行以後加入:
Java代碼
  1. @interface PlayerDetailsViewController : UITableViewController <GamePickerViewControllerDelegate>  

在PlayerDetailsViewController.m加入prepareForSegue方法:
Java代碼
  1. - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender   
  2. {   
  3.     if ([segue.identifier isEqualToString:@"PickGame"])   
  4.     {   
  5.         GamePickerViewController *gamePickerViewController =   
  6.           segue.destinationViewController;   
  7.         gamePickerViewController.delegate = self;   
  8.         gamePickerViewController.game = game;   
  9.     }   
  10. }  

這和咱們以前作過的很類似,可是此次的目標view Controller使game picker場景了,請記住,這個方法必須在GamePickerViewController初始化以後可是尚未加載view的時候調用。

「game」變量是新的,咱們必須聲明他:
Java代碼
  1. @implementation PlayerDetailsViewController   
  2. {   
  3.     NSString *game;   
  4. }  

咱們使用這個變量來記錄到底選擇了哪一個Game,咱們得給這個String設置一個默認值,能夠用initWithCoder方法來完成。
Java代碼
  1. - (id)initWithCoder:(NSCoder *)aDecoder   
  2. {   
  3.     if ((self = [super initWithCoder:aDecoder]))   
  4.     {   
  5.         NSLog(@"init PlayerDetailsViewController");   
  6.         game = @"Chess";   
  7.     }   
  8.     return self;   
  9. }  

若是你以前是用過nibs的話,那麼initWithCode可能會對你很熟悉,這部分在storyboard是同樣的。

修改 viewDidLoad 方法以下,以便單元格可以顯示選中的Game名稱:
Java代碼
  1. - (void)viewDidLoad   
  2. {   
  3.     [super viewDidLoad];   
  4.     self.detailLabel.text = game;   
  5. }  

最後要作的就是執行代理方法:
Java代碼
  1. #pragma mark - GamePickerViewControllerDelegate   
  2.   
  3. - (void)gamePickerViewController:   
  4.   (GamePickerViewController *)controller   
  5.   didSelectGame:(NSString *)theGame   
  6. {   
  7.     game = theGame;   
  8.     self.detailLabel.text = game;   
  9.     [self.navigationController popViewControllerAnimated:YES];   
  10. }  

這行代碼很好懂,我就很少講了。

咱們的結束方法將會把選中的遊戲的名字加入到新建的Player對象中。
Java代碼
  1. - (IBAction)done:(id)sender   
  2. {   
  3.     Player *player = [[Player alloc] init];   
  4.     player.name = self.nameTextField.text;   
  5.     player.game = game;   
  6.     player.rating = 1;   
  7.     [self.delegate playerDetailsViewController:self didAddPlayer:player];   
  8. }  

OK,到這裏咱們就完成了遊戲選擇器的場景,不錯吧。
[轉載][IOS] <wbr>Storyboard全解析-第二部分
相關文章
相關標籤/搜索