不好意思各位,本人休息了一個禮拜,所以這次的進度延后了,而且這次的學習的內容比較多,時間用的也比較長,文章發布的時間間隔有些長了,望各位諒解,下面繼續我們的ios之旅。
這次我們主要學習的內容有2個,一個是Tab Bar,如下圖
很熟悉的界面(iphone中的phone),另一個Picker,如下圖
在正是開始學習項目之前,先首先簡單介紹一下這次的例子的一個結構,當然一個root controller肯定是有的,用來控制其他subController的切換,在root controller中會放置一個tab bar,這個tab bar中有5個item,分別對應5個不同的view,每個view中有一個picker,所以在這個例子中,會有5個功能不同的picker,基本上涵蓋了日常可能會用到的常規情況。OK,下面開始這次的學習。
1)創建一個工程,選擇Empty Application,並命名為Pickers
這個過程和前一篇是一樣的,在這里就不再敘述了。
2)添加需要的文件
之前我們說過,這個項目會有5個subcontrollers,因此我們需要添加5個view,選中Project navigator中的Pickers文件夾,單擊鼠標右鍵,選擇“New File...”,或者使用快捷鍵command+N,或者使用菜單欄File>New>New File...
在彈出的窗口中,左邊選擇Cocoa Touch,右邊選擇UIViewController subclass,點擊Next按鈕。
在下一個窗口中,填寫類名BIDDatePickerViewController,並確認“With XIB for user interface” checkbox被選中(在前一篇的學習中,我們沒有選中這個checkbox,而是在之后手動的連接controller和xib文件,在這里系統將幫我們連接這2個文件),點擊Next
選擇保持的位置,保存在“Pickers”目錄下,完成創建。創建完成后的Project navigator如下
一共有3個文件被創建,分別是.h、.m和.xib。
使用相同的方法再創建其他4個subcontroller,分別命名為BIDSingleComponentPickerViewController、BIDDoubleComponentPickerViewController、BIDDependentComponentPickerViewController、BIDCustomPickerViewController。創建完成后的Project navigator如下:
其中:
BIDDatePickerViewController:包含一個Date Picker
BIDSingleComponentPickerViewController:包含一個Picker View,並有一個滾軸1一個component)
BIDDoubleComponentPickerViewController:包含一個Picker View,並有兩個滾軸(2個component),且這2個滾軸是獨立的
BIDDependentComponentPickerViewController:包含一個Picker View,並有兩個滾軸(2個component),且這2個滾軸是聯動的
BIDCustomPickerViewController:包含一個Picker View,並有五個滾軸(5個component),這5個滾軸中的內容是圖片
(溫馨提示:每一步做完后,你可以先build一下你的code,看看有沒有錯誤,有的話可以及時修改,有一些警告應該是正常的,我們並沒有完全完成項目的創建,但是不應該報錯。)
3)添加Root View Controller(就是前面的提到的root controller)
添加root controller選擇的模板和前面的有些不同,選中Project navigator中的Pickers文件夾,使用快捷鍵command+N創建一個新文件,在彈出的選中模板對話框中,左邊選擇User Interface,右邊選擇Empty模板,單擊Next
Device Family保持默認的iPhone,點擊Next,將新建的模板命名為TabBarController.xib,點擊Create,創建完成。然后一個單獨的TabBarController.xib文件會出現在Project navigator中。(Empty模板不會幫我們創建任何.h.m文件,我們會將TabBarController.xib關聯到BIDAppDelegate,因此我們不需要和前面的文件一樣創建.h.m文件,其次Empty模板不會幫我們創建一個默認的View,點擊TabBarController.xib,你會發現xib的窗口中什么東西也沒有,我們需要在之后自己添加)
打開BIDAppDelegate.h文件,添加以下內容
#import <UIKit/UIKit.h> @interface BIDAppDelegate : UIResponder <UIApplicationDelegate> @property (strong, nonatomic) UIWindow *window; @property (strong, nonatomic) IBOutlet UITabBarController *rootController; @end
打開BIDAppDelegate.m文件,添加以下內容
#import "BIDAppDelegate.h" @implementation BIDAppDelegate @synthesize window = _window; @synthesize rootController; - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; // Override point for customization after application launch. [[NSBundle mainBundle] loadNibNamed:@"TabBarController" owner:self options:nil]; [self.window addSubview:rootController.view]; self.window.backgroundColor = [UIColor whiteColor]; [self.window makeKeyAndVisible]; return YES; } ...
這些方法應該比較熟悉了,和之前幾篇的例子很相似。
在此打開TabBarControll.xib,在Object library中找到Tab Bar Controller
並拖動到xib窗口
下面的一步是將TabBarControll.xib關聯到BIDAppDelegate,選中TabBarControll.xib,然后選擇File's Owner,打開identity inspector,Class中選擇BIDAppDelegate
接着打開connections inspector,在Outlets中會出現我們剛在定義在BIDAppDelegate.h中的rootController,鼠標放到rootController右邊的圓圈上,圓圈中會出現加號,然后點擊鼠標拖動到xib窗口中的Tab Bar Controller上,將2者關聯起來,之后在rootController邊上就會顯示Tab Bar Controller了。
4)添加Tab Bar Item
首先可以看一下Tab Bar Controller的結構
一個Tab Bar Controller下面有一個Tab Bar,然后這個Tab Bar下有很多Item(每個Tab Bar Controller默認會有2個tab bar item),每個Item有一個View Controller,每個View Controller下又有一個Tab Bar Item,好了這個就是一個Tab Bar Controller的基本結構,因為我們需要5個tab bar item,下面就添加其他的3個。
在Object Library中找到Tab Bar Item
然后拖動到Tab Bar上,一共拖3個,位置無所謂,添加完tab bar item的結構如下
這2者是一一對應的。
5個tab bar item分別對應5個view,因此我們需要制定2者的對應關系,選中最左邊的一個tab bar item,打開attribute inspector,在NIB Name的下拉列表中選擇BIDDatePickerViewController,然后打開identity inspcetor,在Class中選擇BIDDatePickerViewController(在NIB Name中選擇BIDDatePickerViewController是指定tab bar item對應的view是哪個,在Class中選中BIDDatePickerViewController是指定view的delegate是哪個類),對其他的4個tab bar item都執行上面的操作,分別對應各自的controller。從左到右的順序為:
BIDDatePickerViewController
BIDSingleComponentPickerViewController
BIDDoubleComponentPickerViewController
BIDDependentComponentPickerViewController
BIDCustomPickerViewController
5)為Tab Bar Item添加icon
下載TabBarIcons,並其解壓后連同文件夾一同拖入到Project navigator中,在彈出的“Choose options for adding these files”框中保持默認選項即可,添加完后的Project navigator如下
一般來說,app中圖片基本上都是使用png格式,然后Tab Bar Item的圖片大小為24px * 24px。(感覺xcode還是很只能的,它能夠自己搜索所有的在項目中的圖片文件,然后供用戶進行選擇,無論你圖片放在那個文件夾下,xcode都可以找到)
圖片拖動到項目中后,接下來就是為tab bar item添加圖片了,首先選中最左邊的一個Tab Bar Item,選中的方法是在視圖中點擊2次最左邊的item圖標,第一次我們選中的是view controller,第二次才是真正的tab bar item
第二次選中后,打開Attributes inspector,在Bar Item的Image中找到clockicon.png,這樣icon就設好了,然后將Title命名為Date即可。
其他的四個item都是相同的操作,對應關系如下
BIDDatePickerViewController(clockicon.png,Title:Date)
BIDSingleComponentPickerViewController(singleicon.png,Title:Single)
BIDDoubleComponentPickerViewController(doubleicon.png,Title:Double)
BIDDependentComponentPickerViewController(dependenticon.png,Title:Dependent)
BIDCustomPickerViewController(toolicon.png,Title:Custom)
完成后的樣子
至此,我們對於root controller的所有設置都完成了,編譯運行一下程序,看看效果,之后在進行每個subcontroller的實現。
6)Date Picker
第一個實現的是Date Picker,首先打開BIDDatePickerViewController.h,添加如下代碼
#import <UIKit/UIKit.h> @interface BIDDatePickerViewController : UIViewController @property (strong, nonatomic) IBOutlet UIDatePicker *datePicker; - (IBAction)buttonPressed; @end
聲明一個UIDatePicker的Outlet,用於等會和DatePicker控件連接,然后聲明一個Action事件,我們會添加一個button,會觸發該事件。
接着打開BIDDatePickerViewController.xib,然后在Graphical Layout Area(簡稱GLA)中選中View,再打開Attributes inspector,在Simulated Metrics中找到Bottom Bar,並選擇“Tab Bar”,如下
設置完成后,在GLA中會出現一條黑色區域,表示Tab Bar area,這樣這個View的實際大小會把Tab Bar area給去掉,實際的高度變成411px
在Object library中找到Date Picker
並拖動到View的頂部,接着拖一個button放在DatePicker的下方,並命名為Select,完成后的效果如下
我們還需要一個設置,設置DatePicker的最大日期和最小日期,選中View上的DatePicker,然后打開Attributs inspector,在Date Picker欄找到Constraints,將2個checkbox勾上
(其他的設置選項你自己也可以隨便玩玩,看看DatePicker有什么變化)
7)連接Outlet和Action,寫code
選中View上的button,打開connections inspector,在Sent Events中找到Touch Up Inside,將其連接到File's Owner下的buttonPressed action
選中File's Owner,control-drag到View中的DatePicker上,選擇Outlet:datePicker
好了,所有的連接都完成了,下面就可以寫code了
打開BIDDatePickerViewController.m,添加如下code
@implementation BIDDatePickerViewController @synthesize datePicker; ...... - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view from its nib. NSDate *now = [NSDate date]; [datePicker setDate:now animated:NO]; } - (void)viewDidUnload { [super viewDidUnload]; // Release any retained subviews of the main view. // e.g. self.myOutlet = nil; self.datePicker = nil; } ...... - (IBAction)buttonPressed { NSDate *selected = [datePicker date]; NSString *message = [[NSString alloc] initWithFormat:@"The date and time you selectd is: %@", selected]; UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Date and Time Selected" message:message delegate:self cancelButtonTitle:@"Yes, I do" otherButtonTitles:nil]; [alert show]; }
這些code應該比較容易理解,在viewDidLoad中初始化datePicker顯示的日期,在viewDidUnload中釋放datePicker,在buttonPressed方法中,首先獲取datePicker當前選中的時間對象NSDate,然后賦給一個string,最后alert顯示出來,非常簡單。
編譯運行code
點擊Select按鈕,一個alert框彈出
一切正常唯有時間不對,iphone上方顯示的是11:13 AM,但是alert中顯示的時間是03:11:13 +0000,這是它顯示的是格林尼治時間,和中國的時差有8個小時,要解決這個問題,改寫buttonPressed如下
- (IBAction)buttonPressed { NSDate *selected = [datePicker date]; NSTimeZone *zone = [NSTimeZone systemTimeZone]; NSInteger interval = [zone secondsFromGMTForDate: selected]; NSDate *localeDate = [selected dateByAddingTimeInterval: interval]; NSString *message = [[NSString alloc] initWithFormat:@"The date and time you selectd is: %@", loaclDate]; UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Date and Time Selected" message:message delegate:self cancelButtonTitle:@"Yes, I do" otherButtonTitles:nil]; [alert show]; }
NSTimeZone *zone = [NSTimeZonesystemTimeZone]; // 獲得當前設備的時區,即iphone的時區
NSInteger interval = [zone secondsFromGMTForDate: selected]; // 算出當前時區和GMT的時差是多少
NSDate *localeDate = [selected dateByAddingTimeInterval: interval]; // 在GMT的基礎上加上加上相應的時差
好了,在此編譯運行,點擊button后,顯示的時間和iphone上的一致了
8)Single-Component Picker
DatePicker是一個特殊的picker,專門用於對日期進行選擇,但是在很多時候,我們需要選擇的內容都是需要我們自己制定的,在這個例子中,我們就將制定自己內容。
在這里例子中,我們會添加一個Picker View
在Picker中,每一個滾軸就是一個component,我們將使用delegate來定義Picker View中component的數量和數據的來源。
9)創建Outlet和Action
打開BIDSingleComponentPickerViewController.h,添加如下code
#import <UIKit/UIKit.h> @interface BIDSingleComponentPickerViewController : UIViewController <UIPickerViewDelegate, UIPickerViewDataSource> @property (strong, nonatomic) IBOutlet UIPickerView *singlePicker; @property (strong, nonatomic) NSArray *pickerData; - (IBAction)buttonPressed; @end
首先添加了2個protocols,這2個protocols是必須的,PickerView的delegate和datasource都是源於它,然后聲明了一個指向PickerView的對象,再聲明一個保存PickerView數據的數列,最后聲明了一個action,代碼很簡單。
10)添加Picker View,綁定Outlet和Action
選中BIDSingleComponentPickerViewController.xib,空出底部的Tab bar位置(Attributes inspector>Simulated Metrics>Bottom Bar>Tab Bar。在Object library中,找到Picker View,拖動到View的頂部,在拖一個button放在Picker View的下面,命名為Select,所有控件添加完畢
選中File's Owner,control-drag到Picker View上,選中singlePicker。
選中Picker View,打開connections inspector,拖動dataSource和delegate邊上的圓圈到File's Owner上進行綁定,這樣就告訴這個Picker View,它的dataSource和delegate都是BIDSingleComponentPickerViewController類
綁定Select按鈕的Touch Up Inside的action為buttonPressed(方法和之前的一個例子一樣)
ok,至此所有的控件和連接操作都已經完成,下面開始code部分。
11)寫code
打開BIDSingleComponentPickerViewController.m,添加如下代碼
#import "BIDSingleComponentPickerViewController.h" @implementation BIDSingleComponentPickerViewController @synthesize singlePicker; @synthesize pickerData; ...... - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view from its nib. NSArray *array = [[NSArray alloc] initWithObjects:@"Luke", @"Leia", @"Han", @"Chewbacca", @"Artoo", @"Threepio", @"Lando", nil]; self.pickerData = array; } - (void)viewDidUnload { [self setSinglePicker:nil]; [super viewDidUnload]; // Release any retained subviews of the main view. // e.g. self.myOutlet = nil; self.singlePicker = nil; self.pickerData = nil; } ...... - (IBAction)buttonPressed { NSInteger row = [singlePicker selectedRowInComponent:0]; NSString *selected = [pickerData objectAtIndex:row]; NSString *title = [[NSString alloc] initWithFormat:@"You selected %@!", selected]; UIAlertView *alert = [[UIAlertView alloc] initWithTitle:title message:@"Thank you for choosing." delegate:nil cancelButtonTitle:@"You're Welcome" otherButtonTitles:nil]; [alert show]; }
一些必要的解釋,在viewDidLoad中,聲明一個數組,存放在PickerView的componnent中顯示的數據。
buttonPressed方法中:
NSInteger row = [singlePicker selectedRowInComponent:0]; // selectedRowInComponent方法返回當前component滾到哪一行,后面的0表示最左邊的一個component
NSString *selected = [pickerData objectAtIndex:row]; // objectAtIndex:row返回component中當前行的對象,這個例子中是NSString
其他的代碼都比較簡單,下面繼續添加code,這次添加dataSource和delegate需要使用到的方法,這也是每個Picker View(不包括Date Picker)都需要實現的方法
#pragma mark - #pragma mark Picker Data Source Methods - (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView { return 1; } - (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component { return [pickerData count]; } #pragma mark Picker Delegate Methods - (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component { return [pickerData objectAtIndex:row]; }
numberOfComponentsInPickerView告訴picker一共有幾個component(滾軸),我們這個例子是SingleComponent,只有一個滾軸,因此返回1。這個方法有一個參數是傳一個pickerview進來,如果一個view中有2個或2個以上的Picker View,就需要使用這個參數,告訴系統到底是在設置哪個Picker View的component的數量,如果只有1個Picker View就可以忽略該參數,我們這個例子就忽略了這個參數。
pickerView方法是返回dataSource的數量,告訴pick你有多少個數據需要顯示。參數pickerView指明是哪個picker在調用,只有一個的話可以忽略該參數,參數component是picker中的第幾個滾軸,如果只有一個滾軸,也可以忽略該參數。
以上是的2個方法都是屬於dataSource,最后的一個方法是屬於delegate的,Picker View的dataSource方法是必須實現的,而delegate方法是可選的,但必須要實現其中的一個,在這個例子中我們實現的是pickerView方法
pickerView方法在指定的picker和component中返回第row行數據,這個例子中的數據都來自NSArray,就返回array中的第row個數據即可。在這個方法中,只用到了參數row,其他2個參數不需要,因為我們只有1個pickerview和1個component。
至此,所有的Single component的所有code都寫完了,編譯運行,選擇Tab Bar上的第二個item
單擊Select按鈕,警告框彈出,顯示哪一行的內容被選中了
13)Multicomponent Picker
在這個例子中,將會有2個component,它們各自獨立(沒有關聯),實現起來也很簡單,只要准備2個NSArray,在返回component的數量的方法中返回2就可以了,下面進行具體的實現。
14)創建Outlet和Action
打開BIDDoubleComponentPickerViewController.h,添加如下code
#import <UIKit/UIKit.h> #define kfillingComponent 0 #define kBreadComponent 1 @interface BIDDoubleComponentPickerViewController : UIViewController <UIPickerViewDelegate, UIPickerViewDataSource> @property (strong, nonatomic) IBOutlet UIPickerView *doublePicker; @property (strong, nonatomic) NSArray *fillingTypes; @property (strong, nonatomic) NSArray *breadTypes; - (IBAction)buttonPressed; @end
上面的code應該很容易理解,定義了2個宏變量來區分左右2個component,這是一個很好的習慣,而且使代碼更易讀,然后添加了2個protocols,聲明了一個指向PickerView的outlet,聲明2個數組,分別保存左右2個component的數據,最后定義一個action,很簡單。
15)添加Picker View,綁定Outlet和Action
打開BIDDoubleComponentPickerViewController.xib,設置view的Bottom Bar為Tab Bar,拖一個Picker View放到view的頂部,拖一個按鈕放到Picker View的下面,並命名為Select,完成后的界面
選中File's Owner,control-drag到Picker View上,選中doublePicker。
選中Picker View,打開connections inspector,拖動dataSource和delegate邊上的圓圈到File's Owner上進行綁定,這樣就告訴這個Picker View,它的dataSource和delegate都是BIDDoubleComponentPickerViewController類
綁定Select按鈕的Touch Up Inside的action為buttonPressed
16)寫code
打開BIDDoubleComponentPickerViewController.m文件,添加如下代碼
@implementation BIDDoubleComponentPickerViewController @synthesize doublePicker; @synthesize fillingTypes; @synthesize breadTypes; - (IBAction)buttonPressed { NSInteger fillingRow = [doublePicker selectedRowInComponent:kfillingComponent]; NSInteger breadRow = [doublePicker selectedRowInComponent:kBreadComponent]; NSString *filling = [fillingTypes objectAtIndex:fillingRow]; NSString *bread = [breadTypes objectAtIndex:breadRow]; NSString *message = [[NSString alloc] initWithFormat:@"Your %@ on %@ bread will be right up.", filling,bread]; UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Thank you for your order" message:message delegate:nil cancelButtonTitle:@"Great!" otherButtonTitles:nil]; [alert show]; } ...... - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view from its nib. NSArray *fillingArray = [[NSArray alloc] initWithObjects:@"Ham", @"Turkey", @"Peanut Butter", @"Tuna Salad", @"Nutella", @"Roast Beef", @"Vegemite", nil]; self.fillingTypes = fillingArray; NSArray *breadArray = [[NSArray alloc] initWithObjects:@"White", @"Whole Wheat", @"Rye", @"Sourdough", @"Seven Grain",nil]; self.breadTypes = breadArray; } - (void)viewDidUnload { [super viewDidUnload]; // Release any retained subviews of the main view. // e.g. self.myOutlet = nil; self.doublePicker = nil; self.fillingTypes = nil; self.breadTypes = nil; } ...... #pragma mark - #pragma mark Picker Data Source Methods - (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView { return 2; } - (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component { if(component == kBreadComponent) return [self.breadTypes count]; return [self.fillingTypes count]; } #pragma mark Picker Delegate Methods - (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component { if(component == kBreadComponent) return [self.breadTypes objectAtIndex:row]; return [self.fillingTypes objectAtIndex:row]; }
上面的code和在singlepicker中的基本一樣,由於有2個component,因此在viewDidLoad中創建了2個數組,在numberOfComponentsInPickerView中返回2,這樣PickerView就會創建2個component(滾軸),在DataSource的pickerView方法中,根據component來返回數組,在delegate的pickerView中根據component返回數據。很簡單,然后編譯運行程序,載入app后,選擇Tab Bar上的第三個tab item,出現如下界面
隨便滾動2個component,他們是獨立的,不會相互影響
點擊Select按鈕,一個警告框彈出,告知你選擇了哪些數據
17)Dependnet Components
前面的2個例子分別實現了1個componet和2個component,在這個例子中,也將實現2個component,但是這2個componet是聯動的,當左邊的component存放美國的各個洲(state),右邊的component存放每個洲的郵政編碼(zip),當左邊的洲發生時,右邊的郵政編碼也會隨之變化。另外在之前的例子中,顯示在component中的數據都是寫在code中的,這個不利於數據的更新,且如果數據量巨大,我們也不可能都寫在code里面,在這個例子中,數據將存入一個property list(plist)文件,component的數據將從該文件中獲取。
打開BIDDependentComponentPickerViewController.h文件,添加如下code
#import <UIKit/UIKit.h>
#define kStateComponent 0
#define kZipComponent 1 @interface BIDDependentComponentPickerViewController : UIViewController <UIPickerViewDelegate, UIPickerViewDataSource> @property (strong, nonatomic) IBOutlet UIPickerView *picker; @property (strong, nonatomic) NSDictionary *stateZips; @property (strong, nonatomic) NSArray *states; @property (strong, nonatomic) NSArray *zips; - (IBAction)buttonPressed; @end
唯一不同的是,多了一個NSDictionary對象,這個對象是用來存放關聯數據的,存放之前提到的plist。
和前面2個例子一樣,拖動一個Picker View和button到View上,並連接Outlet和Action
18)NSDictionary:statedictionary.plist
下載statedictionary.plist壓縮包,並解壓出里面的文件statedictionary.plist,將statedictionary.plist拖入到Project navigator中,放到Pickers目錄下,點擊statedictionary.plist文件,你會發現有茫茫多的數據
其實statedictionary.plist是一個xml文件,理解有序的將這些數據組織起來,供程序使用
19)寫code
由於這次的code稍微有些不同,主要是在讀取plist上面,因此我們分開寫,打開BIDDependentComponentPickerViewController.m文件,首先添加如下code
#import "BIDDependentComponentPickerViewController.h" @implementation BIDDependentComponentPickerViewController @synthesize picker; @synthesize stateZips; @synthesize states; @synthesize zips; - (IBAction)buttonPressed { NSInteger stateRow = [picker selectedRowInComponent:kStateComponent]; NSInteger zipRow = [picker selectedRowInComponent:kZipComponent]; NSString *state = [states objectAtIndex:stateRow]; NSString *zip = [zips objectAtIndex:zipRow]; NSString *title = [[NSString alloc] initWithFormat:@"You select zip code %@.", zip]; NSString *message = [[NSString alloc] initWithFormat:@"%@ is in %@", zip, state]; UIAlertView *alert = [[UIAlertView alloc] initWithTitle:title message:message delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil]; [alert show]; }
上面的code應該不需要過多的解釋了吧,很簡單,應該都可以看懂,接着添加viewDidLoad
- (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view from its nib. NSBundle *boundle = [NSBundle mainBundle]; NSURL *plistURL = [boundle URLForResource:@"statedictionary" withExtension:@"plist"]; NSDictionary *dictionary = [NSDictionary dictionaryWithContentsOfURL:plistURL]; self.stateZips = dictionary; NSArray *components = [self.stateZips allKeys]; NSArray *sorted = [components sortedArrayUsingSelector:@selector(compare:)]; self.states = sorted; NSString *selectedState = [self.states objectAtIndex:0]; NSArray *array = [stateZips objectForKey:selectedState]; self.zips = array; }
這里面的變化比較大,首先聲明了一個NSBundle對象,這個對象在前幾篇的例子中已經出現過(load一個xib),這里做一個比較詳細的解釋:bundle是一個特殊的文件夾,其中包含了程序會使用到的資源,這些資源包含了如圖像、聲音、編譯好的代碼、xib文件。對應bundle,cocoa提供了類NSBundle。
下面逐個對語句進行解釋
NSBundle *bundle = [NSBundle mainBundle]; //這里的作用就是獲取應用程序的主目錄,然后在主目錄中尋找需要的資源
NSURL *plistURL = [boundle URLForResource:@"statedictionary" withExtension:@"plist"]; //通過主目錄來獲取資源的路徑(URL),這里尋找的資源是statedictionary.plist,有了資源的路徑,就可以找到該資源
NSDictionary *dictionary = [NSDictionary dictionaryWithContentsOfURL:plistURL]; //NSDictionary就是通過資源的路徑來獲取資源,並讀取里面的數據
self.stateZips = dictionary; //將dictionary賦stateZips
NSArray *components = [self.stateZips allKeys]; //獲取statedictionary.plist中所有的<key>
NSArray *sorted = [components sortedArrayUsingSelector:@selector(compare:)]; //將獲取的所有<key>按字母序進行排列(這里我只知道是排序的意思,后面的方法我還沒有深究,先這樣子用着)
self.states = sorted; //將排完序的數組賦給states(左邊的component)
NSString *selectedState = [self.states objectAtIndex:0]; // 當啟動程序的時候,左邊的component默認是選擇第一個值的,這里獲得第一個值,然后根據這個值找到對應的zips,然后顯示在右邊的component中
NSArray *array = [stateZips objectForKey:selectedState]; // 根據<key>來獲得對應的zips
self.zips = array; //然后將數組賦給zips
繼續寫code
- (void)viewDidUnload { [super viewDidUnload]; // Release any retained subviews of the main view. // e.g. self.myOutlet = nil; self.picker = nil; self.stateZips = nil; self.states = nil; self.zips = nil; }
不多解釋了
#pragma mark - #pragma mark Picker Data Source Methods - (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView { return 2; } - (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component { if(component == kStateComponent) return [self.states count]; return [self.zips count]; }
Picker的DataSource方法,第一個方法返回2,說明有2個component,第二個方法返回component中數據的個數,這里以component做為區分,如果是kStateComponent,返回左邊component數據的格式,否則返回右邊component數據的個數。
#pragma mark Picker Delegate Methods - (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component { if(component == kStateComponent) return [self.states objectAtIndex:row]; return [self.zips objectAtIndex:row]; } - (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component { if(component == kStateComponent) { NSString *selectedState = [self.states objectAtIndex:row]; NSArray *array = [stateZips objectForKey:selectedState]; self.zips = array; [picker selectRow:0 inComponent:kZipComponent animated:YES]; [picker reloadComponent:kZipComponent]; } }
Picker的delegate方法,第一個方法還是和以前一樣的,第二個方法是重點,用來更新右邊component的數據,更新的方法和viewDidLoad中的方法是一樣的,首先判斷是不是左邊的component發生了變化,如果是,得到左邊component的當前值,根據這個值獲取相應的zips,然后將zips賦值給右邊的component,接着選中右邊component的第一個值,並以動畫的形式選中,滾動到第一個值,最后重新載入右邊的component。
好了,以編譯運行程序了,程序運行后,選擇第四個tabitem
試着改變左邊component的選項,右邊的zips會跟着改變
程序似乎是做完了,但是並不完美,稍微往下滾動左邊的component,會發現一些state的名字太長,沒有完全顯示出來,但是又發現右邊的component有很多空白的地方被浪費了,因此最后能夠拉長左邊的component,縮短右邊的component,方法很簡單,在BIDDependentComponentPickerViewController.m中添加下面的方法
- (CGFloat)pickerView:(UIPickerView *)pickerView widthForComponent:(NSInteger)component { if(component == kZipComponent) return 90; return 200; }
該方法也是一個delegate,它用來設置pickerview中各個component的寬度,再次編譯運行程序,這次程序界面完美了
點擊button,一個警告框彈出,告知你選擇的內容
20)Custom Picker
Picker View中component所包含的對象不僅僅是string,也可以是其他,比如圖片,在這個例子中,我們將做一個簡單的游戲,類似於賭博機中的搖連續的“777”,一共會有5個component,每個component中包含5張不同的圖片,然后根據隨機數進行滾動,如果恰巧有連續3個component選中的圖片一樣,則獲得勝利。
打開BIDCustomPickerViewController.h文件,添加如下code
#import <UIKit/UIKit.h> @interface BIDCustomPickerViewController : UIViewController <UIPickerViewDelegate, UIPickerViewDataSource> @property (strong, nonatomic) IBOutlet UIPickerView *picker; @property (strong, nonatomic) IBOutlet UILabel *winLabel; @property (strong, nonatomic) NSArray *column1; @property (strong, nonatomic) NSArray *column2; @property (strong, nonatomic) NSArray *column3; @property (strong, nonatomic) NSArray *column4; @property (strong, nonatomic) NSArray *column5; - (IBAction)spin; @end
這里多聲明了一個UILabel的outlet,當我們贏得一局后,label會顯示“WIN”,否則label不顯示,因此我們需要一個outlet來指向label。然后聲明了5個NSArray,因為有5個component,所以需要5個NSArray與之對應。
打開BIDCustomPickerViewController.xib,拖一個Picker View放在view的頂部,選中Picker View,在Attributes inspector中找到Interaction,將User Interaction Enabled前的勾去掉,這樣就不能用手指去滾動component了。
然后拖一個Label放在Picker View下面,將Label的邊框拉長到左右兩邊出現輔助線的位置,並在Attributes inspector中將Label的字體大小設置為48,顏色設為你喜歡的顏色即可,Label文字居中,在選中Label的狀態下,使用快捷鍵command + =使Label邊框的大小根據Label字體的大小進行改動。再拖一個button放在Label下面,並命名為Spin。
接着就是連接outlet和action了,PickerView連接到picker outlet,Label連接到winLabel outlet,button連接到spin action,PickerView的dataSource和delegate連接到File's Owner。這些應該都很簡單了,和之前的一樣。
21)添加圖片資源,繼續寫code
首先下載圖片資源:CustomPickerImages,解壓縮后拖入當Project navigator的Pickers目錄下
里面一共有6張png圖片
打開BIDCustomPickerViewController.m文件,添加如下代碼
#import "BIDCustomPickerViewController.h" @implementation BIDCustomPickerViewController @synthesize picker; @synthesize winLabel; @synthesize column1; @synthesize column2; @synthesize column3; @synthesize column4; @synthesize column5; - (IBAction)spin { BOOL win = NO; int numInRow = 1; int lastVal = -1; for (int i = 0; i < 5; i++) { int newValue = random() % [self.column1 count]; if(newValue == lastVal) numInRow++; else numInRow = 1; lastVal = newValue; [picker selectRow:newValue inComponent:i animated:YES]; [picker reloadComponent:i]; if (numInRow >= 3) { win = YES; } } if (win) winLabel.text = @"WIN"; else winLabel.text = @""; }
就解釋一下spin方法中的for循環中
for (int i = 0; i < 5; i++) {
int newValue = random() % [self.column1 count]; //通過產生隨機數,來確定component滾動到哪個位置,要對產生的隨機數進行取余操作,否則很容易就超出component對象的個數,導致內存溢出報錯。這里的余數表示component滾動到第幾個位置
if(newValue == lastVal) //相等時,說明隨機數的余數和前一次的余數相等
numInRow++; //計數器加1
else
numInRow = 1; //計數器重新置1,從新開始計數,最開始的時候計數器賦的值是-1,它不可能和任何余數相等,第一個component也不可能和任何其他的component進行對比,因此第一次執行for循環總是會跳到這里
lastVal = newValue; //記錄最近一次的余數,用於下次比較
[picker selectRow:newValue inComponent:i animated:YES]; //設置picker中當前的component滾動到哪個位置
[picker reloadComponent:i]; //重新載入當前的component
if (numInRow >= 3) { //如果numInRow的值大於等於3,說明有連續3個相同的圖標出現了
win = YES; //連續出現3個相同的圖標,贏得本局
}
}
繼續添加viewDidLoad和viewDidUnload
- (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view from its nib. winLabel.text = @""; UIImage *seven = [UIImage imageNamed:@"seven.png"]; UIImage *bar = [UIImage imageNamed:@"bar.png"]; UIImage *crown = [UIImage imageNamed:@"crown.png"]; UIImage *cherry = [UIImage imageNamed:@"cherry.png"]; UIImage *lemon = [UIImage imageNamed:@"lemon.png"]; UIImage *apple = [UIImage imageNamed:@"apple.png"]; for (int i = 1; i <= 5; i++) { UIImageView *sevenView = [[UIImageView alloc] initWithImage:seven]; UIImageView *barView = [[UIImageView alloc] initWithImage:bar]; UIImageView *crownView = [[UIImageView alloc] initWithImage:crown]; UIImageView *cherryView = [[UIImageView alloc] initWithImage:cherry]; UIImageView *lemonView = [[UIImageView alloc] initWithImage:lemon]; UIImageView *appleView = [[UIImageView alloc] initWithImage:apple]; NSArray *imageViewArray = [[NSArray alloc] initWithObjects:sevenView, barView, crownView, cherryView, lemonView, appleView, nil]; NSString *fieldName = [[NSString alloc] initWithFormat:@"column%d", i]; [self setValue:imageViewArray forKey:fieldName]; } srandom(time(NULL)); } - (void)viewDidUnload { [super viewDidUnload]; // Release any retained subviews of the main view. // e.g. self.myOutlet = nil; self.picker = nil; self.winLabel = nil; self.column1 = nil; self.column2 = nil; self.column3 = nil; self.column4 = nil; self.column5 = nil; }
就解釋一下viewDidLoad中某些語句(viewDidUnload很簡單,就不做說明了):
UIImage *seven = [UIImage imageNamed:@"seven.png"]; //imageNamed方法是根據圖片的名稱去尋找圖片並載入
UIImageView *sevenView = [[UIImageView alloc] initWithImage:seven]; //在for循環中根據UIImage對象創建6個UIImageView
NSArray *imageViewArray = [[NSArray alloc] initWithObjects:sevenView, barView, crownView, cherryView, lemonView, appleView, nil]; //創建一個Array,大家應該可以猜到,這個數組會賦給component,NSArray保存的對象是object,因此不論是string還是UIImageView,都可以保持在里面
NSString *fieldName = [[NSString alloc] initWithFormat:@"column%d", i]; //之前聲明了5個NSArray,它們的名字都是有規律的column*
[self setValue:imageViewArray forKey:fieldName]; //setValue:forKey:是根據屬性(property)的名字來為其賦值,我們使用這個方法,來為5個column*賦值
srandom(time(NULL)); //設置隨機數的因子,可以是每次產生的隨機數不同
最后添加dataSource和delegate方法
#pragma mark - #pragma mark Picker Data Source Methods - (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView { return 5; } - (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component { return [self.column1 count]; } #pragma mark Picker Delegate Methods - (UIView *)pickerView:(UIPickerView *)pickerView viewForRow:(NSInteger)row forComponent:(NSInteger)component reusingView:(UIView *)view { NSString *arrayName = [[NSString alloc] initWithFormat:@"column%d", component + 1]; NSArray *array = [self valueForKey:arrayName]; return [array objectAtIndex:row]; }
DataSource方法和之前的並無兩樣,就不解釋了,而delegate方法則稍稍有些不同,之前返回的董事NSString,這里返回的UIView,因為component中的對象是UIImageView
NSString *arrayName = [[NSString alloc] initWithFormat:@"column%d", component + 1]; //參數component傳過來的值是0~4,所以必須要加1,才能對應到column1~column5
NSArray *array = [self valueForKey:arrayName]; //valueForKey根據property的名字得到對象
return [array objectAtIndex:row]; //根據得到的array返回第row個對象
22)編譯運行
選擇第五個tabitem,程序的初始界面
點擊多次Spin后,終於獲勝
23)總結
這篇文章主要講解了TabBar和PickerView的使用,TabBar作為root controller,控制了5個subview的切換,PickerView有2種,一種是DatePicker,另一種是PickerView,DatePicker專門處理日期,比較簡單。PickerView相對復雜一些,需要引入<UIPickerViewDelegate, UIPickerViewDataSource>,並使用其中的方法告知PickerView中有多少個component和每個component中有多少個對象,還要綁定NSArray,並自己寫返回對象的方法。
24)一個小問題
如果你和我使用的是同一個版本的xcode
那在編譯項目的時候,始終會有一個感嘆號存在
TabBarController.xib: warning: Attribute Unavailable: Defines Presentation Context is not available prior to Xcode 4.2.
我也不太清楚是什么意思,但是查了一下網,選中TabBarController.xib,在Attributes inspector中,把Defines Context前的勾去掉就可以,然后編譯就沒有感嘆號了。