Bookmark and Share

iPhone SDK Tutorial
Chapter 6. Picker Views




6.0 Preview

This chapter is largely based on Beginning iPhone 3 Development

In this example, we are going to build a five content views using picker control.



6.1 DataSources and Delegates

Pickers are much more complicated than the other controls we've seen so far. That's mainly because we have to provide it with both a picker delegate and a picker datasource.

The picker defers to its delegate asking to do several jobs. The most important one is the task of determining what to draw for each of the rows in each of picker components.

On top of the delegate, pickers need to have datasource whose job is to retrieve data from the model and pass it along to the picker. The datasource tells the picker how many components and rows are there.





6.2 Five View Controllers and Five Classes

Let's create a new project, while keeping the checkbox for "Use Core Data for Storage" unchecked, select the Window-based Application template and type in the name "Pickers". Then, we will have following files that Xocde created for us.

New Proj Pickers

We need five view controllers. Our root controller will swap those views in and out in our application later.
Create a new file and select "Cocoa Touch Classes", then select UIViewController subclass. Check "With XIB for user interface", then press Next.

UIViewController Subclass

Name it as "DatePickerController.m". Then Finish.

DatePickerViewController_m

Then we see three new files in our Classes folder:

  • DatePickerViewController.h
  • DatePickerViewController.m
  • DatePickerViewController.xib

The third file, "*.xib", should be moved to Resources folder.

Three DatePickerController Files

Repeat the step for the following four names.

  • SinglePickerViewController.m
  • DoublePickerViewController.m
  • LinkedPickerViewController.m
  • SlotPickerViewController.m

Then, we see all the files we made.

Five PickerViewController Made


6.3 Root View Controller

We will make an instance of UITabBarController and use it as our root view controller. So, we need to declare an outlet for it.

Let's put that outlet into PickersAppDelegate.h.

#import <UIKit/UIKit.h>

@class PickersViewController;

@interface PickersAppDelegate : NSObject <UIApplicationDelegate> {
	IBOutlet UIWindow *window;
	IBOutlet UITabBarController *rootController;
}

@property (nonatomic, retain) UIWindow *window;
@property (nonatomic, retain) UITabBarController *rootController;
@end

As before, this class conforms to a protocal UIApplicationsDelegate.
Now PickersAppDelegate.m's turn.

#import "PickersAppDelegate.h"

@implementation PickersAppDelegate

@synthesize window;
@synthesize rootController;

- (void)applicationDidFinishLaunching:(UIApplication *)application {	
	
    // Override point for customization after app launch
    [window addSubview:rootController.view];
    [window makeKeyAndVisible];
}

- (void)dealloc {
	[rootController release];
	[window release];
	[super dealloc];
}

@end

Now it's time to move to tab bars. We need icons for tab bars which use icons to represent each of the tabs. Here, are those 24 x 24 icons in png format.

dateicon singleicon doubleicon linkedicon sloticon

Put those icons into the Resources folder.

Then, open Interface Builder by double-clicking the MainWindow.xib file. Drag a Tab Bar Controller from the library over to the nib' main window, MainWindow.xib.

TabBar To MainWindow.xib

A new window will appear and his tab bar controller will be our root controller. The root controller controls the very first view that the user sees when our application runs.

TabBarController

Select the Tab Bar Controller icon in nib's main window, open up the attribute inspector from Tools top menu.

Attribute TabBarController

We need to change our tab bar controller so it has five tabs instead of two pressing a button with + sign three times.

Increase Tabs To Five

When we look at the Tab Bar Controller window, we see five tab buttons instead of two.

Tab Bar Controller Window With 5 Tabs

Click the leftmost tab bar at the bottom of the Tab Bar Controller window. This should select the controller that corresponds to the leftmost tab, and the inspector should change to look like the picture below.

item 1 Tab Window

This is where we link each tab's view controller with the appropriate nib. This leftmost tab will launch the first of our five subviews. We will leave everything untouched and use it as it is for our application.

Bring up the identity inspector and change the class to DatePickerViewController.

DateViewController Name Identity

Back to the attributes inspector. Click the first tab in the tab bar, click it again. This should make inspector change again, so the only small text which is Item 1 under the icon will be highlighted. Inspector now should look like the picture below.

First Tab Bar Item Attributes

By clicking the tab bar again, we've changed the selection from the view controller associated with the tab bar item to the tab bar item itself. In other words, the first click selected the first of the five subview's view controllers. The second click selects the tab bar item so that we can set its title and icon.

In our project, we do not use the first item labeled Badge and the second item labeled Identifier. The next two fields are where we will specify a tile and tab icon. Do as shown in the picture.

DateIcon Attributes

Don't worry about the view controller Title field. We do not use it. We are using the tab bar item Title field not the view controller Title field.

Repeat the steps to the other four tabs.

Now control-drag from the PickersAppDelegate icon to the Tab Bar Controller icon, select the rootController outlet.

Outlet RootController

Save nib, go back to Xcode, and run.
It should work.

iPhoneResultA

Though the tab bar and the content views have been hooked up, there is nothing in the content views now. We have some problem for the icons. Some of them are not displayed properly, especially, the icons which are not medium gray icons.




6.4 Date Picker

We need an outlet to grab the value from the date picker and we need an action to throw up an alert to show the date value pulled from the picker.

"DatePickerViewController.h"

#import <UIKit/UIKit.h>

@interface DatePickerViewController : UIViewController {
	IBOutlet	UIDatePicker	*datePicker;
}
@property (nonatomic, retain) UIDatePicker *datePicker;
-(IBAction)buttonPressed:(id)sender;
@end

Now, it's ready to setup content view.

Save the "DatePickerViewController.h" file. Open up content view for this first tab by double-clicking "DatePickerViewController.xib".
Bring up the attribute inspector from Tools top menu after selecting the View icon. Set the Bottom Bar popup to Tab Bar which is under the Simulated Interface Elements, then it will reduce the view's height to 411 pixels as in the picture below.

Sizing DatePickerView

What are we going to do with this View?
DatePicker! Where do we get it? From the Library.

Library DatePicker

Let's work on the date picker. Bring up attributes inspector. Check Minimum and Maximum Date check boxes to set Constraints.

DatePicker Attributes

Put a Round Rect Button and label it Select. Then, from the connections inspector, drag Touch Up Inside event over to File's Owner icon, and connect to the buttonPressed action.

SelectConnection

Contri-drag from the File's Owner icon back to the date picker, and select the datePicker outlet.

FileOwner To DatePicker

Save nib, go back to Xcode.

Time for coding, let's look at "DatePickerViewController.m"

#import "DatePickerViewController.h"

@implementation DatePickerViewController
@synthesize datePicker;

-(IBAction)buttonPressed:(id)sender
{
	NSLog(@"Button Pressed");
	NSDate *selected = [datePicker date];
	NSString *message = [[NSString alloc] initWithFormat:@"The date and time you selected is: %@", selected];
	UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Date and Time Selected" message:message delegate:nil cancelButtonTitle:@"Yes, I did." otherButtonTitles:nil];
	[alert show];
	[alert release];
	[message release];
	
}
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
	if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {
		// Initialization code
	}
	return self;
}


- (void)viewDidLoad {
	NSDate *now = [NSDate date];
	[datePicker setDate:now animated:YES];
}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
	// Return YES for supported orientations
	return (interfaceOrientation == UIInterfaceOrientationPortrait);
}


- (void)didReceiveMemoryWarning {
	[super didReceiveMemoryWarning]; // Releases the view if it doesn't have a superview
	// Release anything that's not essential, such as cached data
}


- (void)dealloc {
	[datePicker release];
	[super dealloc];
}

@end

Let's look at what we've done to the code.

We synthesized the setter and getter for our datePicker outlet. Then, we implemented buttonPressed and overrides viewDidLoad.
In buttonPressed action method, we used datePicker outlet to obtain the current date from the datepicker. Then, we constructed a string with that date and used it to display an alert sheet.

In viewDidLoad, we created a new NSDate object which will hold the current date and time. Then, we set datePicker to that date.

Build and Run.

The two picture below shows the it's working. At least for datePicker. The second picture shows alert sheet when the Select button clicked.

iPhoneDatepickerResultA iPhoneDatepickerResultB



6.5 Single Picker

Unlike the date pickers, in this example, we will let the user select from a list of values. So, we need to create NSArray to hold the values to display in the picker.

Pickers don't hold any data themselves. Instead, they call methods on their datasource and delegate to get the data they need. The pickers doesn't care where the underlying data is. It just asks for the data when it needs it. The datasoruce and delegate work together to supply the data.

So, the data could be coming from a static list or could be loaded from a file or a URL, or even could be calculated on the run.

We should put our outlets and actions in place in the controller's header file before we start to work with Interface Builder.

Let's put some code into the "SinglePickerViewController.h"

#import <UIKit/UIKit.h>


@interface SinglePickerViewController : 
     UIViewController <UIPickerViewDelegate, UIPickerViewDataSource> {
	IBOutlet	UIPickerView *singlePicker;
				NSArray	*pickerData;
}
@property (nonatomic, retain) UIPickerView *singlePicker;
@property (nonatomic, retain) NSArray *pickerData;
- (IBAction)buttonPressed:(id)sender;
@end

Note that we make our controller class conforming to two protocols, UIPickerViewDelegate
and UIPickerViewDataSource.
Then we declared an outlet for the picker and a pointer to an NSArray, and finally the action method, buttonPressed.

Let's build the view.

Open up the content view for the Single tab in our tab bar.
Bring up the attributes inspector to set the Bottom Bar to Tab Bar in the Simulated Interface Elements section. Then put the Library's Picker View into nib's View window.

BuildSinglePickerView

Now we have the picker in the View, we need to set the outlet. Control-drag from File's Owner icon to the picker view, and select singlePicker outler.

SingleFileOwnerToPickerView

Bring up connections inspector. Drag the circle from dataSource to File's Owner

DataSource To FileOwner Single

Drag delegate connection to File's Owner icon, then the connections inspector should look like this.

Connections Inspector Single

Now this picker knows that the instance of the SinglePickerViewController class in the nib is its datasource and delegate. Picker will ask the instance to supply the data to be displayed. So, when the picker needs info about the data it is going to display, the picker asks the SinglePickerView instance which controls this view for that info.

Drag a Round Rect Button to the view, labeled it Select. From the connections inspector, connect the Touch Up Inside to File's Owner icon, select buttonPress action.

TouchUp Inside Single

Save nib, back to Xcode.


Our controller needs to work properly as the picker's datasource and delegate. To do that, we have to implement a few methods.

Let's look at "SinglePickerViewController.m"

#import "SinglePickerViewController.h"


@implementation SinglePickerViewController
@synthesize singlePicker;
@synthesize pickerData;

- (IBAction)buttonPressed:(id)sender
{
	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];
	[alert release];
	[title release];
}

- (void)viewDidLoad {
	NSArray *array = [[NSArray alloc] initWithObjects:@"Gustav Klimt", @"LeiaPablo Picasso", 
					  @"Vincent van Gogh", @"Pierre-Auguste Renoir", @"Peter Paul Rubens ", 
					  @"Jackson Pollock", @"Paul Cézanne", nil];
	self.pickerData = array;
	[array release];
	
}
....

Unlike the date picker, a normal picker does not know what date it holds, because it doesn't maintain the data. It hands that job over to the delagate and datasource. So, we have to ask the picker which row is selected and then get the corresponding data from the pickerData array.

Let's look at the first line of buttonPressed method:

NSInteger row = [singlePicker selectedRowInComponent:0];

Note that we had to specify which component we want to know about. Fortunately, we have only one component in this picker. So, we simply pass in 0, which is the index of the first component.

We create an array with several objects in viewDidLoad method so that we have data to feed the picker. Because we embedded a list of items in our code, it is much harder if we need to update this list. But this is the quickest and easiest way to get data into an array.

Finally, insert the following new code at the end of the file:

#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];
}
@end

In the code, the line:

- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView
{
	return 1;
}

Pickers can have more than one spinning wheel and this is how the picker asks how many components it should display. We want to display only one list this time, so we return 1. Note that a UIPickerView is passed in as a parameter. This parameter points to the picker view that is asking us the question. This makes it possible to have multiple pickers being controlled by the same datasource. In our example, we know that we have only one picker, so we can safely ignore this argument because we already know which picker is calling us.

The second datasource method:

- (NSInteger)pickerView:
  (UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component
{
	return [pickerData count];
}

is used by the picker to ask how many rows of data.

After the two datasource methods, we implement one delegate method.

- (NSString *)pickerView:
  (UIPickerView *)pickerView titleForRow:
       (NSInteger)row forComponent:(NSInteger)component
{
	return [pickerData objectAtIndex:row];
}

In the method, the picker is asking us to provide the data for a specific row in a specific component. We are provided with a pointer to the picker that is asking, along with the component and row that it is asking about. Since our view has one picker with one component, we simply ignore everything except the row argument and use that to return the appropriate item from our data array.

Notice you have a new line of code:

#pragma mark -
#pragma mark Picker Data Source Methods

#pragma is usually a compiler directive, however, in this case, it is a directive to the IDE. The first one puts a line in the menu and the second one creates a bold entry in the editor.

Pragma Picker

So, the final version of "SinglePickerViewController.m" is:

#import "SinglePickerViewController.h"


@implementation SinglePickerViewController
@synthesize singlePicker;
@synthesize pickerData;

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
	if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {
		// Initialization code
	}
	return self;
}

- (IBAction)buttonPressed:(id)sender
{
	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];
	[alert release];
	[title release];
}

- (void)viewDidLoad {
	NSArray *array = [[NSArray alloc] initWithObjects:@"Gustav Klimt", @"LeiaPablo Picasso", 
					  @"Vincent van Gogh", @"Pierre-Auguste Renoir", @"Peter Paul Rubens ", 
					  @"Jackson Pollock", @"Paul Cézanne", nil];
	self.pickerData = array;
	[array release];
	
}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
	// Return YES for supported orientations
	return (interfaceOrientation == UIInterfaceOrientationPortrait);
}

- (void)didReceiveMemoryWarning {
	[super didReceiveMemoryWarning]; // Releases the view if it doesn't have a superview
	// Release anything that's not essential, such as cached data
}

- (void)dealloc {
	[singlePicker release];
	[pickerData release];
	[super dealloc];
}
#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];
}
@end

Build and Run.

We get the following results.

iPhone Single Result A iPhone Single Result B



6.6 Double Picker

In this example, we will have a picker with two wheels or components. Each wheel will be independent of the other wheel. The left wheel have a list of OS, and the right wheel will have a selection of their languages.

We will have the same datasource and delegate methods that we had for the single picker. But we have to write a little additional code in some of those methods.

Let's work on outlets and actions.

Look at "DoublePickerViewController.h"

#import <UIKit/UIKit.h>

#define kOSComponent 0
#define kLangComponent 1

@interface DoublePickerViewController : 
    UIViewController <UIPickerViewDelegate, UIPickerViewDataSource> {
	IBOutlet UIPickerView *doublePicker;
	NSArray *osTypes;
	NSArray *langTypes;
	
}
@property(nonatomic, retain) UIPickerView *doublePicker;
@property(nonatomic, retain) NSArray *osTypes;
@property(nonatomic, retain) NSArray *langTypes;
-(IBAction)buttonPressed;
@end

Components are assigned numbers, with the leftmost component being assigned zero and increasing by one for each move to the right.

Notice that we conform our controller class to both the delegate and datasource protocols. We also declared an outlet for the picker, as well as for arrays for our two components. After declaring properties for each of our instance variables, as usual we got buttonPressed method.

Now, let's build the view. Double click DoublePickerViewController.xib.

This time, we are not going into the details of how to put buttons, tab bar etc. Here is a summary what to do:

  1. Connect the doublePicker outlet on File's Owner to the picker.
  2. Connect the DataSource and Delegate connections on the picker to File's Owner.
  3. Connect the Touch Up Inside event of the button to the buttonPressed action on the File's Owner.

Save nib.

Let's implement the controller. Look at the file "DoublePickerViewController.m"

#import "DoublePickerViewController.h"

@implementation DoublePickerViewController
@synthesize doublePicker;
@synthesize osTypes;
@synthesize langTypes;

-(IBAction)buttonPressed
{
    NSInteger langRow = [doublePicker selectedRowInComponent:
                          kLangComponent];
    NSInteger osRow = [doublePicker selectedRowInComponent:
                            kOSComponent];
    
    NSString *lang = [langTypes objectAtIndex:langRow];
    NSString *os = [osTypes objectAtIndex:osRow];
    
    NSString *message = [[NSString alloc] initWithFormat:
                         @"Your %@ on %@ language will be right up.",lang, os];
    
    
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:
                          @"Thank you for your choice" 
                                                    message:message 
                                                   delegate:nil 
                                          cancelButtonTitle:@"Great!" 
                                          otherButtonTitles:nil];
    [alert show];
    [alert release];
    [message release];
    
}

- (void)viewDidLoad {
    NSArray *langArray = [[NSArray alloc] initWithObjects:
						   @"C++", @"Java", @"Python",@"Objective_C",
						   @"Ruby", @"Javascript", @"XML", @"HTML5", nil];
    self.langTypes = langArray;
    [langArray release];
    
    NSArray *osArray = [[NSArray alloc] initWithObjects:@"Mac OS X", 
                             @"Linux", @"Android", @"iPhone",@"webOS",
                             @"Windows Mobile", @"Symbian", @"RIM BlackBerry", nil];
    self.osTypes = osArray;
    [osArray release];
}

- (void)didReceiveMemoryWarning {
	// Releases the view if it doesn't have a superview.
    [super didReceiveMemoryWarning];
	
	// Release any cached data, images, etc that aren't in use.
}

- (void)viewDidUnload {
	// Release any retained subviews of the main view.
	// e.g. self.myOutlet = nil;
    self.doublePicker = nil;
    self.langTypes = nil;
    self.osTypes = nil;
    [super viewDidUnload];
}

- (void)dealloc {
    [doublePicker release];
    [langTypes release];
    [osTypes release];
    [super dealloc];
}

#pragma mark -
#pragma mark Picker Data Source Methods

- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView
{
    return 2;
}

- (NSInteger)pickerView:(UIPickerView *)pickerView  
         numberOfRowsInComponent:(NSInteger)component
{
    if (component == kLangComponent)
        return [self.langTypes count];
    
    return [self.osTypes count];
}

#pragma mark Picker Delegate Methods

- (NSString *)pickerView:(UIPickerView *)pickerView 
             titleForRow:(NSInteger)row 
            forComponent:(NSInteger)component
{
    if (component == kLangComponent)
        return [self.langTypes objectAtIndex:row];
    
    return [self.osTypes objectAtIndex:row];
}
@end

The buttonPress method is a little bit more complicated this time. But not much. We just have to specify which component we are dealing with when we requesting the selected row.

NSInteger langRow = [doublePicker selectedRowInComponent:
                          kLangComponent];
NSInteger osRow = [doublePicker selectedRowInComponent:
                            kOSComponent];

Regarding the viewDidLoad: method, the only difference compared to the one we used in the previous section is that we are loading two arrays with data rather than just one.

    NSArray *langArray = [[NSArray alloc] initWithObjects:
						   @"C++", @"Java", @"Python",@"Objective_C",
						   @"Ruby", @"Javascript", @"XML", @"HTML5", nil];
    NSArray *osArray = [[NSArray alloc] initWithObjects:@"Mac OS X", 
                             @"Linux", @"Android", @"iPhone",@"webOS",
                             @"Windows Mobile", @"Symbian", @"RIM BlackBerry", nil];

Let's look at the following line of code:

- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView
{
    return 2;
}

In the method, we specify that our picker should have two components rather than just one.
But this time, when we are asked for the number of rows, we have to check which component the picker is asking about and return the correct row counter for the corresponding array:

- (NSInteger)pickerView:(UIPickerView *)pickerView  
         numberOfRowsInComponent:(NSInteger)component
{
    if (component == kLangComponent)
        return [self.langTypes count];
    
    return [self.osTypes count];
}

Then, in our delegate method, we do the same thing.

- (NSString *)pickerView:(UIPickerView *)pickerView 
             titleForRow:(NSInteger)row 
            forComponent:(NSInteger)component
{
    if (component == kLangComponent)
        return [self.langTypes objectAtIndex:row];
    
    return [self.osTypes objectAtIndex:row];
}


Build and Run.

We get the following results.


iPhone Double Result A iPhone Double Result B




6.7 Linked Picker

This picker will show a list of US states in the left component and a list of Zip codes in the right component depending on the state currently selected.

For each states there will be one entry in a dictionary with the name of the state as the key. An NSArray instance which contains all the ZOP codes from that state will be stored under that key.

Here is "LinkedPickerViewController.h"

#import <UIKit/UIKit.h>

#define kStateComponent 0
#define kZipComponent 1

@interface LinkedPickerViewController : UIViewController  
            <UIPickerViewDelegate, UIPickerViewDataSource> {
	
	IBOutlet UIPickerView *picker;
	
	NSDictionary *stateZips;
	NSArray	*states;
	NSArray *zips;
	
}
@property (retain, nonatomic) UIPickerView *picker;
@property (retain, nonatomic) NSDictionary *stateZips;
@property (retain, nonatomic) NSArray *states;
@property (retain, nonatomic) NSArray *zips;
- (IBAction)butonPressed:(id)sender;
@end

Build the content view. Save nib and back to Xcode.

Though the dependency of one component on the other added a whole new level of complexity to our controller class, our previous code remains virtually untouched because the additional complexity is handled in a different routine.

Before we write the code, we need data to display. Until now, we have created arrays in code using a list of strings. But in this example, with so many data, it's not practical way of using data. So, we are going to load data from a property list. Both NSArray and NSDictionary objects can be created from property list.

Here is the list "statedictionary.plist".

PropertyList State Zip

Add the file into the Resources. Once we added it to the project, we can edit the list.

Here is the file "LinkedPickerViewController.m".

#import "LinkedPickerViewController.h"

@implementation LinkedPickerViewController
@synthesize picker;
@synthesize stateZips;
@synthesize states;
@synthesize zips;

- (IBAction)butonPressed:(id)sender
{
	NSInteger stateRow = [picker selectedRowInComponent:kStateComponent];
	NSInteger zipRow = [picker selectedRowInComponent:kZipComponent];
	
	NSString *state = [self.states objectAtIndex:stateRow];
	NSString *zip = [self.zips objectAtIndex:zipRow];
	
	NSString *title = [[NSString alloc] initWithFormat:@"You selected 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];
	[alert release];
	[title release];
	[message release];
}

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
	if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {
		// Initialization code
	}
	return self;
}

- (void)viewDidLoad {	
	NSBundle *bundle = [NSBundle mainBundle];
	NSString *plistPath = [bundle pathForResource:@"statedictionary" ofType:@"plist"];
	NSDictionary *dictionary = [[NSDictionary alloc] initWithContentsOfFile:plistPath];
	self.stateZips = dictionary;
	[dictionary release];
	
	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;
}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
	// Return YES for supported orientations
	return (interfaceOrientation == UIInterfaceOrientationPortrait);
}

- (void)didReceiveMemoryWarning {
	[super didReceiveMemoryWarning]; // Releases the view if it doesn't have a superview
	// Release anything that's not essential, such as cached data
}

- (void)dealloc {	
	[picker release];
	[stateZips release];
	[states release];
	[zips release];
	[super dealloc];
}
#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];
}

#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];
	}
}

- (CGFloat)pickerView:(UIPickerView *)pickerView widthForComponent:(NSInteger)component
{
	if (component == kZipComponent)
		return 90;
	return 205;
}
@end

Let's talk about the viewDidLoad method. The first thing we do is grab a reference to our application's main bundle.

NSBundle *bundle = [NSBundle mainBundle];

Here, a bundle is the primary way of getting to the resources we added to the Resources folder of our project.

If we want to get to those resources in our code, we usually have to use NSBundle. We use the main bundle to retrieve the path of the resource in which we are interested:

NSString *plistPath = 
   [bundle pathForResource:@"statedictionary" ofType:@"plist"];

It will return a string containing the location of the file, "statedictionary.plist". After that, we can use that path to create a NSDictionary object. Then we assign it to stateZips.

NSDictionary *dictionary = [[NSDictionary alloc] initWithContentsOfFile:plistPath];
self.stateZips = dictionary;
[dictionary release];

In the dictionary, the name of the states are the keys and it contains an NSArray with all the ZIP codes for that state. To populate the array for the first component, we get the list of all keys from out dictionary and assign those to the states array preceded by alphabetical sorting.

NSArray *components = [self.stateZips allKeys];
NSArray *sorted = [components sortedArrayUsingSelector:@selector(compare:)];
self.states = sorted;

Let's look at one of the datasource method.

- (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];
	}
}

The method is called whenever the picker's selection changes. We look at the component and see whether the left-hand component changed. If it did, we grab the array corresponding to the new selection and assign it to the zips array. Then we set the right-hand component back to the first row and tell it to reload itself.

In the code, the following lines make the width of the two component different.

- (CGFloat)pickerView:(UIPickerView *)pickerView widthForComponent:(NSInteger)component
{
	if (component == kZipComponent)
		return 90;
	return 205;
}

Build and Run.



iPhone Linked Result A iPhone Linked Result B



6.8 Slot Picker

Not implemented