在開始這章之前,先做個說明,從這篇開始,我所使用的xcode更新成了最新的版本,版本是4.6.1(4H512),如下:
大家可以打開自己電腦上的App Store,然后搜索xcode,第一個出現的就是Xcode,然后直接點擊安裝就行,很方便且智能,如果你的電腦上有舊版本的xcode,它還會提示你刪除,反正整個過程我按住下來還是很容易的。
另外,從這篇開始,我使用的教程也做了相應的升級,現在使用的教程為
這個大家去搜一下就可以找到,很方便。
好了,其他的沒什么不同,下面開始我們這一篇的學習。
1)Storyboard簡介
這次學習的內容是在iOS 5的時候才加入的一個新的東西:Storyboard,簡單的翻譯成“故事版”(好吧,我覺得這個名字蠻挫的),它簡化了一些開發app時的一些步驟,例如自動為我們添加必要的delegate/dataSource,在多個view之間的切換,使用圖和線連接各個view,讓我們能夠清晰的看到各個view之間的前后關系。這樣的好處是減輕了我們在管理view之前切換時的工作量,能夠讓我們把更多的注意力集中在具體的功能實現上,然后是我們對整個的app view之間的關系有一個整體的了解。
Storyboard還是基於xib(Xcode's Interface Builder),但是一個Storyboard中又可以包含多個xib,每個xib都一個它自己的controller綁定。好像,下面先舉一個簡單的例子,來直觀的感受一下什么是Storyboard。
2)Create a Simple Storyboard
創建一個project,左邊選擇Application,右邊選擇Single View Application,點擊Next
將項目命名為“Simple Storyboard”,然后選中Use Storyboards,單擊Next
找個地方保存新建的項目,完成創建
在project navigator中,默認幫我們創建的文件有很多都是和之前一樣的,有BIDAppDelegate,BIDViewController,但是我們沒有發現xib文件,取而代之的是一個MainStoryboard.storyboard,在這個storyboard中,藏着一個系統默認幫我們創建的xib,選中MainStoryboard.storyboard,在editor area中,會出現一個xib,而整個xib的controller文件正是BIDViewController,至此所有默認創建的文件都已經對上號了。
打開dock,選中View Controller節點並展開,你會發現,在layout area下的一個黑色區域中顯示的圖標和dock中是一樣的,這個黑色區域和上方的view組成了一個場景,叫做scene。(在scene中還有一個Exit,這個就不作介紹了,因為書本里面也是省略的)在view的左邊有一個大大的箭頭,這個箭頭是用來說明該app的起始view是哪個。
在layout area的右下方有一個小圖標,這個是用來切換iphone4和iphone5的(我們的這個例子還是基於iphone4的界面)
好了,簡單的介紹就到這里,下面繼續我們這個例子,從Object library中拖一個Label放到view的中間,雙擊Label,輸入“Simple”
好了編譯運行你的程序,一個最簡單的Storyboard app完成了
當我們使用Storyboard開發app的時候,很多事情程序都會幫我們完成,包括如何載入默認的xib。如果你選中project navigator中的項目名稱
在editing pane中你可以找到程序默認載入的storyboard,這里例子中默認的storyboard是MainStoryboard.storyboard
3)Storyboard with UITableViewController
在之前幾篇的例子中,我們已經很多次的使用到了UITableViewController,對其操作的方式應該已經很熟悉了,一般是tableview中包含很多個cell,每個cell有一個identifier,創建cell的時候用到的方法是cellForRowAtIndexPath。在storyboard中,還是會用到這些,但是會相對簡單一些,下面我們接着上面的例子做下去。
選中Project navigator中的Simple Storyboard文件夾,單擊鼠標右鍵,選擇“New File...”,在彈出的窗口中,左邊選擇Cocoa Touch,右邊選擇Objective-C class,點擊Next按鈕,在下一個窗口中將class命名為BIDTaskListController,Subclass of命名為UITableViewController,點擊Next按鈕,完成創建。
接着選中MainStoryboard.storyboard,從Object library中拖一個Table View Controller到layout area
在dock中,選中剛才拖入的Table View Controller(這樣能夠抱着我們選中的是layout area中整個的Table View Controller)
打開Identity inspector,將Class設置為BIDTaskListController,當設置完成后,dock中的table view會變成Task List Controller
這樣我們新加的TableViewController就和它的類對應起來了,tableview也知道它可以去哪里取得它所需要的數據。
在拖入的Table View Controller上,有一個默認的cell(Prototype Cells),我們可以為其添加identifier,在其上面定制自己的cell樣式(注意,我們可以添加任意多個Prototype Cells,每個cell用identifier來區分,定制不同的樣式,這里的cell只是一個原型,根據原型cell生成正式的cell,之后會有一個這樣的例子)。我們選中整個默認的cell,並打開attributes inspector,找到Identifier,輸入plainCell
然后從object library中,拖一個Label放到原型cell上,Label如何布局看個人愛好,選中Label,在attributes inspector中找到Tag,將其值設為1,這樣在code中就可以通過Tag的值找到Label了。
接着,我們選中整個的cell,你也可以到dock中去選,這樣比較准確,然后按Command + D,或者從菜單欄中選擇Edit>Duplicate,復制一個cell,這樣在table view中就有2個prototype cell了
(這里有一個非常有用的小技巧,當你想直接在view中選擇自己想要的元素時,但是又礙於一個view上疊加的元素太多很難直接選中,那么在這時,你同時按住鍵盤上的shift和control鍵,然后在你想選擇的元素上點擊鼠標,會彈出一個窗口,上面羅列了鼠標點擊的位置下所有存在的元素,然后你再去進行選擇會變的異常的簡單。
例如我在cell中的Label上點鼠標
鼠標點擊的位置一共有4個層疊元素
如果我們在cell上點擊鼠標
Label不見了,只有三個元素。
這么樣,用這樣的方法去選擇元素很簡單吧,還是蠻佩服蘋果在細節方面的考慮和設計的。)
選中新加的cell,在attributes inspector中將Identifier賦值為attentionCell
選中Label,在attributes inspector中將其顏色設置為紅色
好了,對於這個table view的操作全部完成,在開始具體的編寫代碼之前,還有一件事情,將layout area中的那個大大的箭頭移到這個table view上,這樣程序在載入的時候默認的會顯示這個view
保存一下MainStoryboard.storyboard,下面開始具體的編碼。
打開BIDTaskListController.m文件,你會發現很多常用的方法已經存在:
viewDidLoad
didReceiveMemoryWarning
numberOfSectionsInTableView
numberOfRowsInSection
cellForRowAtIndexPath
didSelectRowAtIndexPath
我們只要直接往這些方法中填代碼就可以了,添加如下代碼
#import "BIDTaskListController.h" @interface BIDTaskListController () @property (strong, nonatomic) NSArray *tasks; @end @implementation BIDTaskListController ...... - (void)viewDidLoad { [super viewDidLoad]; // Uncomment the following line to preserve selection between presentations. // self.clearsSelectionOnViewWillAppear = NO; // Uncomment the following line to display an Edit button in the navigation bar for this view controller. // self.navigationItem.rightBarButtonItem = self.editButtonItem; self.tasks = @[@"Walk the dog", @"URGENT: Buy milk", @"Clean hidden lair", @"Invent miniature dolphins", @"Find new henchmen", @"Get revenge on do-gooder heroes", @"URGENT: Fold laundry", @"Hold entire world hostage", @"Manicure"]; } ...... #pragma mark - Table view data source - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { #warning Potentially incomplete method implementation. // Return the number of sections. return 1; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { #warning Incomplete method implementation. // Return the number of rows in the section. return [self.tasks count]; }
首先和之前一樣,定義一個NSArray類型的tasks,用於保存table view中的行,然后在viewDidLoad中對tasks進行賦值(這里的語法和之前我看到的賦值方法有點不同,越到后面,語句寫的越是精煉啊),在numberOfSectionsInTableView中,返回1,表示只有一個section,在numberOfRowsInSection中返回section中row的數量。這些都很簡單,接着添加代碼
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *CellIdentifier = @"Cell"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath]; NSString *identifier = nil; NSString *task = [self.tasks objectAtIndex:indexPath.row]; NSRange urgentRange = [task rangeOfString:@"URGENT"]; if (urgentRange.location == NSNotFound) { identifier = @"plainCell"; } else { identifier = @"attentionCell"; } UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier]; // Configure the cell... UILabel *cellLabel = (UILabel *)[cell viewWithTag:1]; NSMutableAttributedString *richTask = [[NSMutableAttributedString alloc] initWithString:task]; NSDictionary *urgentAttributes = @{NSFontAttributeName : [UIFont fontWithName:@"Courier" size:24], NSStrokeWidthAttributeName : @3.0}; [richTask setAttributes:urgentAttributes range:urgentRange]; cellLabel.attributedText = richTask; return cell; }
代碼一開始定義了一個identifier,然后根據indexPath獲得tasks中的task,NSRange可以認為是一個范圍或者一種排列,它根據這個范圍或者排列到另一個對象去尋找,如果找到了就返回開始的位置,如果沒有找到就返回NSNotFound。NSRange的對象urgentRange定義了一個字段“URGENT”,它在task中進行匹配,如果存在,那么這個cell就把plainCell賦給identifier,如果不存在則將attentionCell賦給identifier。然后根據identifier從tableView的方法dequeueReusableCellWithIdentifier中得到相應的cell。
之后的一段是對cell上的label進行操作,還記得剛才我們在attributes inspector中將Label的Tag設置為1了嗎?這里就用到了,使用viewWithTag的方法在cell中找到Label,然后對Label進行賦值,之后的一些操作是對特定的字符串“URGENT”進行操作,更改它的字體屬性。這些就一筆帶過吧,畢竟我們的注意力不是集中在這個上面,對Label操作完后,將其賦給cellLabel,最后返回cell。
好了,編譯運行(編譯時會有warning產生,這個不用去理會,編譯還是會成功,這些warning是告訴你在Storyboard中有xib是沒有被使用的,我們的箭頭沒有指向它且和當前的view又沒有聯系,所以不會對其進行操作,有warning是正常的),效果如下
如果task中包含字符串“URGENT”那么將會使用identifier為attentionCell的cell,否則就使用identifier為plainCell的cell。
4)Static Cells
在大部分的情況下,table view中的cell都是需要動態生成了,app運行時,根據source的不同生成不同數量或者樣式的cell,但是在某些情況下,我們可以事先知道將要生成的cell是什么樣子的,且它是固定不變的,我們把這樣的cell稱之為Static Cells,與其對應的則是dynamic cell。在這里我們舉一個簡單的例子來說明這種情況。
我們不用創建一個新的project,直接在上面的程序中接着添加代碼。選中Project navigator中的Simple Storyboard文件夾,單擊鼠標右鍵,選擇“New File...”,在彈出的窗口中,左邊選擇Cocoa Touch,右邊選擇Objective-C class,點擊Next按鈕,在下一個窗口中將class命名為BIDStaticCellsController,Subclass of命名為UITableViewController,點擊Next按鈕,完成創建。
選中MainStoryboard.storyboard,再從Object library中拖一個Table View Controller到layout area,就放在原有2個view的右邊,接着將箭頭指向這個新添加的view
圖中最右邊的是新添加的view,這些view看上去比較小,是因為我了layout area右下角的,這樣可以方便觀察每一個view(當然在縮小的狀態下,是沒有辦法對view進行操作的,只能移動其位置,要操作view,必須將view放大回正常的大小)
選中剛才添加的controller中的table view,打開attributes inspector,找到Content,在其下拉框中選擇“Static Cells”,找到Style,在其下拉框中選擇“Grouped”
table view的樣式也隨之發生了變化,出現了3行row,section的樣式變成了一個圓角矩形
選中section,在其attributes inspector設置如下,Rows改為2,Header中填寫“Silliest Clock Ever”
改完后的section
下面對2個cell進行設置,選中第一個cell,在attributes inspector中將其Style設置為“Left Detail”
然后雙擊Title,改成“The Date”,重復上面的步驟,將第二個cell的Title改成“The Time”,改完后的效果
之后,我們將創建兩個outlet對象,分別指向2個Detail,這樣在app運行后,就可以改變它們的值了。
現在先關聯這個table view controller和它的類,在dock中選中Table View Controller,然后打開identity inspector,在Class中輸入“BDIStaticCellsController”,dock中的名字也隨之發生改變
還是在dock中選中controller的狀態下,將Editor的模式設置成Assistant editor,這樣BIDStaticCellsController.h文件會打開(如果打開的不是這個文件,那么就手動打開吧),選中第一個cell中的Detail,然后control-drag到BIDStaticCellsController.h中並釋放,會彈出個窗口,將Name命名為“dateLabel”
對第二個cell中的Detail進行相同的操作,將Name命名為“timeLabel”,添加完成后的BIDStaticCellsController.h
#import <UIKit/UIKit.h> @interface BIDStaticCellsController : UITableViewController
@property (weak, nonatomic) IBOutlet UILabel *dateLabel; @property (weak, nonatomic) IBOutlet UILabel *timeLabel; @end
下面開始編寫代碼,打開BIDStaticCellsController.m,先將下面三個方法刪除
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { #warning Potentially incomplete method implementation.
// Return the number of sections.
return 0; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { #warning Incomplete method implementation.
// Return the number of rows in the section.
return 0; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *CellIdentifier = @"Cell"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath]; // Configure the cell...
return cell; }
因為我們使用的是static cell,因此table view中section的數量,section中cell的數量都是固定不變的,我們也不需要從新創建cell,cell一共才2個,會一直顯示在屏幕上。
接着添加下面的代碼
- (void)viewDidLoad { [super viewDidLoad]; // Uncomment the following line to preserve selection between presentations. // self.clearsSelectionOnViewWillAppear = NO; // Uncomment the following line to display an Edit button in the navigation bar for this view controller. // self.navigationItem.rightBarButtonItem = self.editButtonItem; NSDate *now = [NSDate date]; self.dateLabel.text = [NSDateFormatter localizedStringFromDate:now dateStyle:NSDateFormatterLongStyle timeStyle:NSDateFormatterNoStyle]; self.timeLabel.text = [NSDateFormatter localizedStringFromDate:now dateStyle:NSDateFormatterNoStyle timeStyle:NSDateFormatterLongStyle]; }
在viewDidLoad中,分別對dateLabel和timeLabel進行了設置,至於NSDate和NSDateFormatter的說明大家就去google一下吧,這里不做詳細解釋了。
編譯運行,效果如下