在開始這章之前,先做個說明,從這篇開始,我所使用的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,但是在某些情況下,我們可以事Crowdsourced Security Testing道將要產生的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一下吧,這裡不做詳細解釋了。
編譯運行,效果如下
Simple Storyboard