ios開發環境搭建
故事版(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文件,看看那上麵是怎麼說的:
#import <UIKit/UIKit.h> @interface AppDelegate : UIResponder <UIApplicationDelegate> @property (strong, nonatomic) UIWindow *window; @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,加入
#import <UIKit/UIKit.h> #import "AppDelegate.h" int main(int argc, char *argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } } |
之前是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(可變數組)
#import <UIKit/UIKit.h> @interface PlayersViewController : UITableViewController @property (nonatomic, strong) NSMutableArray *players; @end |
這個數組將會包含我們的應用的主要數據模型。我們現在加一些東西到這個數組之中,新建一個使用Obj-c模板的文件,命名為player,設置喂NSObject的子類,這將會作為數組的數據容器。
編寫Player.h如下:
@interface Player : NSObject @property (nonatomic, copy) NSString *name; @property (nonatomic, copy) NSString *game; @property (nonatomic, assign) int rating; @end |
編寫Player.m如下:
#import "Player.h" @implementation Player @synthesize name; @synthesize game; @synthesize rating; @end |
這裏沒有什麼複雜的,Player類隻是一個容器罷了,包含三個內容:選手的名字、項目和他的評級。
接下來我們在App Delegate中聲明數組和一些Player對象,並把他們分配給PlayerViewController的players屬性。
在AppDelegate.m中,分別引入(import)Player和PlayerViewController這兩個類,之後新增一個名叫players的可變數組。
#import "AppDelegate.h" #import "Player.h" #import "PlayersViewController.h" @implementation AppDelegate { NSMutableArray *players; } // Rest of file... |
修改didFinishLaunchingWithOptions方法如下:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { players = [NSMutableArray arrayWithCapacity:20]; Player *player = [[Player alloc] init]; player.name = @"Bill Evans"; player.game = @"Tic-Tac-Toe"; player.rating = 4; [players addObject:player]; player = [[Player alloc] init]; player.name = @"Oscar Peterson"; player.game = @"Spin the Bottle"; player.rating = 5; [players addObject:player]; player = [[Player alloc] init]; player.name = @"Dave Brubeck"; player.game = @"Texas Hold’em Poker"; player.rating = 2; [players addObject:player]; UITabBarController *tabBarController = (UITabBarController *)self.window.rootViewController; UINavigationController *navigationController = [[tabBarController viewControllers] objectAtIndex:0]; PlayersViewController *playersViewController = [[navigationController viewControllers] objectAtIndex:0]; playersViewController.players = players; return YES; } |
這將會創造一些Player對象並把他們加到數組中去。之後在加入:
UITabBarController *tabBarController = (UITabBarController *) self.window.rootViewController; UINavigationController *navigationController = [[tabBarController viewControllers] objectAtIndex:0]; PlayersViewController *playersViewController = [[navigationController viewControllers] objectAtIndex:0]; playersViewController.players = players; |
咦,這是什麼?目前的情況是:我們希望能夠將players數組連接到PlayersViewController的players屬性之中以便讓這個VC能夠用做數據來源。但是app delegate根本不了解PlayerViewController究竟是什麼,他將需要在storyboard中尋找它。
這是一個我不是很喜歡storyboard特性,在IB中,你在MainWindow.xib中總是會有一個指向App delegate的選項,在那裏你可以在頂級的ViewController中向Appdelegate設置輸出口,但是在Storyboard中目前這還不可能,目前隻能通過代碼來做這樣的事情。
UITabBarController *tabBarController = (UITabBarController *) self.window.rootViewController; |
我們知道storyboard的起始場景是Tab Bar Controller,所以我們可以直接到這個場景的第一個子場景來設置數據源。
PlayersViewController 在一個NavController的框架之中,所以我們先看一看UINavigationController類:
UINavigationController *navigationController = [[tabBarController viewControllers] objectAtIndex:0]; |
然後詢問它的根試圖控製器,哪一個是我們要找的PlayersViewController:
PlayersViewController *playersViewController = [[navigationController viewControllers] objectAtIndex:0]; |
但是,UIViewController根本就沒有一個rootViewController屬性,所以我們不能把數組加入進去,他又一個topViewController但是指向最上層的視圖,與我們這裏的意圖沒有關係。
現在我們有了一個裝在了players物體合集的數組,我們繼續為PlayersViewController設置數據源。
打開PlayersViewController.m,加入以下數據源方法:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return 1; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [self.players count]; } |
真正起作用的代碼在cellForRowAtIndexPath方法裏,默認的模板是如下這樣的:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *CellIdentifier = @"Cell"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; if (cell == nil) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier]; } // Configure the cell... return cell; } |
無疑這就是以前設置一個表格視圖的方法,不過現在已經革新了,把這些代碼修改如下:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"PlayerCell"]; Player *player = [self.players objectAtIndex:indexPath.row]; cell.textLabel.text = player.name; cell.detailTextLabel.text = player.game; return cell; } |
這看上去簡單多了,為了新建單元格,你隻需使用如下代碼:
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"PlayerCell"]; |
如果沒有現存的單元格可以回收,程序會自動創造一個原型單元格的複製品之後返回給你,你隻需要提供你之前在Storyboard編輯視圖中設置的身份證就可以的,在這裏就是“PlayerCell”,如果不設置這個,這個程序就無法工作。
由於這個類對於Player容器目前一無所知,所以我們需要在文件的開頭加入一個引入來源
#import "Player.h" |
記得要創建synthesize語句哦親
@synthesize players; |
現在運行應用,會看到Table裏有著players容器。
請注意:我們這裏隻使用一種單元格原型,如果你需要使用不同類型的單元格的話,隻需要在storyboard中另外加入一個單元格原型就可以了,不過不要忘記給他們指派不同的身份證。
設計自定義的原型單元格
對於很多應用來說,使用默認的單元格風格就OK了,但是我偏偏要在每一個單元格的右邊加上一個一個圖片來表示選手的評級,但是添加圖片對於默認類型的單元格來說並不支持,我們需要自定義一個設計。
讓我們轉回MainStoryboard.storyboard,選中table view中的prototype cell,把它的Style attribute改為Custom,所有默認的標簽都會消失。
首先把單元格變得更高一些,你可以直接拉它,也可以在大小控製器中修改數字,我在這裏使用55點的高度。
從 Objects Library中拖出兩個標簽物體,按照之前的樣式安插到單元格裏,記得設置label的Highlighted顏色為白色,那樣的話當單元格被選中的時候會看起來更好看一些。
之後添加一個Image View對象,將它放置在單元格的右邊,設置他的寬度為81點,高度並不重要,在屬性檢查器中設置模式為置中。
我把標簽設置為210點長以確保他不會和ImageView重合,最後整體的設計會看起來象下麵這樣:
由於這是一個自定義的單元格,所以我們不能夠使用UITableView默認的textLabel和detailLabel來設置數據,這些屬性也不再指向我們的單元格了,我們使用標簽(tags)來指定標簽。
將Name標簽的tag設置為100,Game的設置喂101,image的設置喂102,在屬性檢查器裏設置哦親。
之後打開 PlayersViewController.m ,在PlayersViewcontroller中將cellForRowatIndexPath修改為:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"PlayerCell"]; Player *player = [self.players objectAtIndex:indexPath.row]; UILabel *nameLabel = (UILabel *)[cell viewWithTag:100]; nameLabel.text = player.name; UILabel *gameLabel = (UILabel *)[cell viewWithTag:101]; gameLabel.text = player.name; UIImageView * ratingImageView = (UIImageView *) [cell viewWithTag:102]; ratingImageView.image = [self imageForRating:player.rating]; return cell; } |
這裏是用了一個新的方法,叫做ImageRating,在 cellForRowAtIndexPath方法之前加入這個方法:
- (UIImage *)imageForRating:(int)rating { switch (rating) { case 1: return [UIImage imageNamed:@"1StarSmall.png"]; case 2: return [UIImage imageNamed:@"2StarsSmall.png"]; case 3: return [UIImage imageNamed:@"3StarsSmall.png"]; case 4: return [UIImage imageNamed:@"4StarsSmall.png"]; case 5: return [UIImage imageNamed:@"5StarsSmall.png"]; } return nil; } |
這就完成了,運行看看:
這和我們想象的結果並不是很符合,我們修改了原型單元格的屬性和高度,但是table view卻沒有考慮進去,有兩種方法可以修複它,我們可以改變table view的行高或者加入 heightForRowAtIndexPath 方法來修改,地一種方法更簡單,我們就用他。
注意:在一下兩種情況下,你應該使用 heightForRowAtIndexPath 方法:一是,你不能預先知道你的單元格的高度,二是不同的單元格會有不同的高度。
回到MainStoryboard.storyboard,在大小檢查器中將高度設置為55:
通過這種方式的話,如果之前你是使用拖動而不是鍵入數值的方式改變高度的屬性的話,則table view的數值也會自動改變。
現在運行看看,好多了吧
為原型單元格設置子類
我們的表格視圖已經相當像模像樣了,但是我並不是很喜歡使用tag來訪問label,要是我們能夠把這些lable連接到輸出口,之後在回應屬性中使用他們,該多好,而且不出所料,我們可以這樣做。
使用 Objective-C class模板新建一個文件,命名為PlayerCell,繼承UITableViewCell。
修改PlayerCell.h
@interface PlayerCell : UITableViewCell @property (nonatomic, strong) IBOutlet UILabel *nameLabel; @property (nonatomic, strong) IBOutlet UILabel *gameLabel; @property (nonatomic, strong) IBOutlet UIImageView *ratingImageView; @end |
修改PlayerCell.m
#import "PlayerCell.h" @implementation PlayerCell @synthesize nameLabel; @synthesize gameLabel; @synthesize ratingImageView; @end |
這個類本身並不其很大的作用,隻是為nameLabel、gameLabel和ratingImageView聲明了屬性。
回到MainStoryboard.storyboard選中原型單元格,將他的class屬性修改為“PlayerCell”,現在當你向table view請求dequeueReusableCellWithIdentifier,他會返回一個PlayerCell實例而不是一個普通的UITableViewCell實例。
請注意我將這個類和reuse Indetifier的名字命名的一樣,隻是營衛我喜歡這樣哦親,這兩個之間其實沒啥關係。
現在你可以將標簽和image view連接到輸出口去了,選中或者將他從鏈接檢查器拖動到table view cell。
I
請注意:要把這個control連接到table view cell而不是view controller哦親,別選錯了。
現在我們把一切都鏈接好了,隻需要加入數據源的代碼就可以了。
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { PlayerCell *cell = (PlayerCell *)[tableView dequeueReusableCellWithIdentifier:@"PlayerCell"]; Player *player = [self.players objectAtIndex:indexPath.row]; cell.nameLabel.text = player.name; cell.gameLabel.text = player.game; cell.ratingImageView.image = [self imageForRating:player.rating]; return cell; } |
我們現在將接收到 dequeueReusableCellWithIdentifier 的控件指派到PlayerCell,隻需要簡單的使用已經鏈接labels和image view到設置好的屬性上就可以了,這會讓這個設計看上去更加好控製,更加簡明。
當然,在PlayerCell前要引入資源:
#import "PlayerCell.h" |
試著運行,你會發現其實什麼都沒有變化,可是我們都知道,內部已經有了變化。
在這相同的場景下麵,我們可是在使用子類呢。
這裏還有一些設計小竅門:第一點:一定要設置標簽被選中時的顏色。
第二點,確保你加入單元格的字符大小是可以變化的,這樣,當單元格大小變化時,他的內容的大小也會跟著變化,比如說:
在PlayersViewController.m中加入如下方法:
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath { if (editingStyle == UITableViewCellEditingStyleDelete) { [self.players removeObjectAtIndex:indexPath.row]; [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade]; } } |
這個方法加入好了之後,用手指輕掃一行單元格,會出現一個刪除鍵,試試看
Delete按鈕出現在右邊,遮住了一部分評級圖片,怎麼解決呢?
打開MainStoryBoard.storyboard,選中table view cell中的image view,在大小檢查器中修改Autosizing屬性,是它能夠跟隨上級view的邊緣。
為labels設置同樣的屬性。
加入了這些變動之後,刪除按鈕如我們意料的出現了:
其實,最好的做法是讓這些星星在出現delete按鈕的時候消失,不過這隻是一個練習,不要太較真哦親
最後更新:2017-04-03 08:26:17