Beginning iPhone Development Exploring the iPhone SDK
Dave Mark Jeff LaMarche
Chapter
7
Tab Bars and Pickers
i
n the last chapter, you built your first multiview application. In this chapter, you’re going to build a full tab bar application with five different tabs and five different content views. Building this application is going to reinforce a lot of what you learned in the last chapter, but you’re too smart to spend a whole chapter doing stuff you already sorta know how to do, so we’re going to use those five content views to show you how to use a type of iPhone control that we have not covered yet. The control is called the picker view, although it’s usually just called the picker. You may not be familiar with the name, but you’ve almost certainly used a picker if you’ve owned an iPhone for more than, say, 10 min‑ utes. Pickers are the controls with dials that spin. You use them to input dates in the Cal‑ endar application or to set a timer in the Clock application (see Figure 7‑1). Pickers are rather more complex than the iPhone controls you’ve seen so far, and as such, they deserve a little more attention. Pickers can be configured to display one dial or many. By default, pickers display lists of text, but they can also be made to display images.
igure 7‑1. A picker in the F Clock application
139
140
CHAPTER 7: Tab Bars and Pickers
The Pickers Application This chapter’s application, Pickers, will feature a tab bar. As you build Pickers, you’ll change the default tab bar so it has five tabs, add an icon to each of the tab bar items, and then cre‑ ate a series of content views and connect each to a tab. The first content view we’ll build will have a date picker, which is the easiest type of picker to implement (see Figure 7‑2). The view will also have a button that, when tapped, will dis‑ play an alert that displays the date that was picked. The second tab will feature a picker with a single list of values (see Figure 7‑3). This picker is a little bit more work to implement than a date picker. You’ll learn how to specify the values to be displayed in the picker by using a delegate and a datasource. In the third tab, we’re going to create a picker with two separate wheels. The technical term for each of these wheels is a picker component, so here, we are creating a picker with two components. We’ll see how to use the datasource and delegate to provide two independent lists of data to the picker (see Figure 7‑4). Each of this picker’s components can be changed without impacting the other one. In the fourth content view, we’re going to build another picker with two components. But this time, the values displayed in the component on the right are going to change based on the value selected in the component on the left. In our example, we’re going to display a list of states in the left component and a list of that state’s ZIP codes in the right component (see F igure 7‑5).
igure 7‑2. The first tab will F show a date picker.
igure 7‑3. A picker displayF ing a single list of values
igure 7‑4. A two- F component picker
CHAPTER 7: Tab Bars and Pickers
And last, but most certainly not least, we’re going to have a little fun with the fifth content view. We’re going to see how to add image data to a picker, and we’re going to do it by writ‑ ing a little game that uses a picker with five components. In several places in Apple’s documentation, the picker’s appear‑ ance is described as looking a bit like a slot machine. Well, then, what could be more fitting than writing a little slot machine game (see F igure 7‑6)? For this picker, the user won’t be able to manually change the values of the components but will be able to select the Spin button to make the five wheels will spin to a new, randomly selected value. If three copies of the same image appear in a row, the user wins.
igure 7‑5. In this picker, F one component is dependent on the other. As we select a state in the left component, the right component changes to a list of ZIP codes in that state.
Delegates and Datasources Before we dive in and start building our application, let’s look at why pickers are so much more complex than the other controls you’ve used so far. It’s not just a matter of there being more configurable attributes to set in Interface Builder. In fact, the picker actually has very few attributes that are con‑ figurable in Interface Builder. With the exception of the date picker, you can’t use a picker by just grabbing one in Interface Builder, dropping it on your content view, and configuring it. You have to also provide it with both a picker delegate and a picker datasource.
igure 7‑6. Our five comF ponent picker. Note that we do not condone using your iPhone as a tiny casino.
By this point, you should be comfortable using delegates. We’ve already used application delegates and action sheet delegates, and the basic idea is the same here. The picker defers several jobs to its delegate. The most important of these is the task of determining what to actually draw for each of the rows in each of its components. The picker asks the delegate for either a string or a view that will be drawn at a given spot on a given component.
141
142
CHAPTER 7: Tab Bars and Pickers
In addition to the delegate, pickers have to have a datasource. The datasource works similarly to the delegate, in that its methods are called at certain, prespecified times. Picker datasource methods are used by the picker to get the number of components and the number of rows in each component. Without a datasource and a delegate specified, pickers cannot do their job and, in fact, won’t even be drawn. It’s very common for the datasource and the delegate to be the same object, and just as common for that object to be the view controller for the picker’s enclosing view, which is the approach we’ll be using in this application. The view controllers for each content pane will be the datasource and the delegate for their picker.
NOTE Here’s a pop quiz: Is the picker datasource part of the model, view, or controller portion of the application? It’s a trick question. A datasource sounds like it must be part of the model, but in fact, it’s actually part of the controller. The datasource isn’t usually an object designed to hold data; it’s an object that supplies data to the picker.
Let’s fire up Xcode and get to it.
Setting Up the Tab Bar Framework Although Xcode does provide a template for tab bar applications, we’re going to build ours from scratch. It’s not much extra work, and it’s good practice. So, create a new project, select‑ ing the W indow-Based Application template again. When prompted for a name, type Pickers. We’re going to walk you through the building of the whole application, but if, at any step of the way, you feel like challenging yourself by moving ahead of us, by all means, go ahead. If you get stumped, you can always come back. If you don’t feel like skipping ahead, that’s just fine. We’d love the company.
Creating the Files In the last chapter, we created a root view controller. We’ll be doing that again this time, but we won’t need to create a class for it, because Apple provides a very good one for managing tab bar views, so we’re just going to use an instance of UITabBarController for our root controller. We will create that instance in Interface Builder in a few minutes.
CHAPTER 7: Tab Bars and Pickers
We need to create five new classes in Xcode: the five view controllers that the root controller will swap in and out. Expand the Classes and Resources folders in the Groups & Files pane. Next, s ingle-click the Classes folder, and press ⌘N or select New File. . . from the File menu. Select Cocoa Touch Classes in the left pane of the new file assistant, and then select the icon for UIViewController subclass, and click Next. Name the first one DatePickerViewController.m, making sure to check Also create “DatePickerViewController.h”. Repeat four more times, using the names SingleComponentPickerViewController.m, DoubleComponentPickerViewController.m, DependentComponentPickerViewController.m, and CustomPickerViewController.m. Next, s ingle-click the Resources folder, and press ⌘N or select New File. . . from the File menu again. This time, you want to select User Interfaces in the left pane of the new file assistant, and then click the View XIB icon. We need five nibs, one for each content view. Call the first one you create DatePickerView.xib. Then create four more: SingleComponentPickerView.xib, DoubleComponentPickerView.xib, DependentComponentPickerView.xib, and CustomPickerView.xib.
Setting Up the Content View Nibs Double-click DatePickerView.xib to open the file in Interface Builder. We’re not actually going to build our content views yet. We’re just going to wire up the skeleton of our tab view application now so that the basic application works. We have to make sure that each of the nib files is pointing to the right File’s Owner icon, which needs to be the correspond‑ ing UIViewController subclass. For DatePickerView.xib, the controller class needs to be DatePickerViewController. Single-click File’s Owner, and then press ⌘4 to bring up the identity inspector. Change the class to DatePickerViewController. Now, control-drag from the File’s Owner icon to the View icon, and select the view outlet. Finally, single-click the View icon, and press ⌘3 to bring up the size inspector. Change the height of the view to 411 pixels, which is the amount of space remaining after you take out the status bar and the tab bar. Save this nib, and close it. Repeat all those same steps with each of the four remaining nib files, making File’s Owner point to the corresponding view controller. Make sure you save each nib file as you finish up, and when you’re done with all five, go back to Xcode.
143
144
CHAPTER 7: Tab Bars and Pickers
Adding the Root View Controller We’re going to create our root view controller, which will be an instance of UITabBarController, in Interface Builder. Before we can do that, however, we should declare an outlet for it. S ingle-click the PickersAppDelegate.h class, and add the following code to it: #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
Before we move to Interface Builder to create our root view controller, let’s add the following code to PickersAppDelegate.m: #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
CHAPTER 7: Tab Bars and Pickers
There shouldn’t be anything there that’s a surprise to you. It’s pretty much the same thing we did in the last chapter, except that we’re not using a controller class provided by Apple; instead, we’re using one we wrote ourselves. Make sure you save both files. Tab bars use icons to represent each of the tabs, so we should also add the icons we’re going to use before heading over to Interface Builder. You can find some suitable icons in the project archive that accompanies this book in the 07 Pickers/Tab Bar Icons/ folder. The icons should be 24 by 24 pixels and saved in .png format. The icon file should have a transparent background. Generally, medium grey icons look the best on a tab bar. Don’t worry about trying to match the appearance of the tab bar. Just as it does with the application icon, the iPhone is going to take your image and make it look just right.
igure 7‑7. Tab Bar Controller in the F library
You should be comfortable adding resources to your project by this point, so go ahead and add the five icons we’ve provided by dragging them from the finder to the Resources folder of your Xcode proj‑ ect or selecing Add to Project. . . from the Project menu. Once you’ve added the icons, double-click MainWindow.xib to open the file in Interface Builder. Drag a Tab Bar Controller from the library (see Figure 7‑7) over to the nib’s main window. Be sure you drag to the window labeled MainWindow.xib and not to the window labeled Window, which will not accept the drag, so you’ll know when you get it right. Once you drop the tab bar controller onto your nib’s main window, a new window will appear that looks like Figure 7‑8. This tab bar controller will be our root view controller. As a reminder, the root view controller controls the very first view that the user will see when your program runs.
igure 7‑8. The tab bar controller’s F window
145
146
CHAPTER 7: Tab Bars and Pickers
Single-click the Tab Bar Controller icon in your nib’s main window, and press ⌘1 to bring up the attributes inspec‑ tor for it. The attributes inspector for a tab bar controller will look like Figure 7‑9. The part that we’re interested in is the top section, which is labeled View Controllers. When all is said and done, we’ll end up with one view controller for each of our tab controller’s tabs. Take a look back at Figure 7‑2. As you can see, our program features five tabs, one for each of our five subviews—five subviews, five view controllers. Turn your attention back to the attributes inspector for the tab bar controller. We need to change our tab bar controller so it has five tabs instead of two. Click the but‑ ton with the plus sign on it three times to create a total of five controllers. The attributes inspector will show five items, and if you look over at the Tab View Controller window, you’ll see that it now has five buttons instead of two.
igure 7‑9. The attributes inspector F for the tab bar controller
Click the tab bar at the bottom of the Tab Bar Controller window. Be sure you click the leftmost tab. This should select the controller that corresponds to the leftmost tab, and the inspector should change to look like Figure 7‑10. Here’s where we associate each tab’s view controller with the appropriate nib. This leftmost tab will launch the first of our five subviews. Leave the Title field blank and specify a NIB Name of DatePickerView. Do not include the .xib extension. While you are here, press ⌘4. This will bring up the identity inspector for the view controller associated with the leftmost tab. Change the class to DatePickerViewController, and press return or tab to set it.
igure 7‑10. The view controller F attributes inspector
NOTE We left Title blank here because tab bar controllers don’t make use of this field, though navigation controllers and many other kinds of view controllers do use this field. We just thought you’d like to know.
CHAPTER 7: Tab Bars and Pickers
Type ⌘1 to return to the attributes inspector. Click the first tab in the tab bar, and click it again in the same spot. This should cause the inspector to change again, so it looks like Figure 7‑11. By clicking the tab bar again in the same spot, we’ve changed the selection from the view controller associ‑ ated 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 itself so that we can set its title and icon. This is where we can specify the tab bar item’s icon Figure 7‑11. The tab bar item and title. Change the Title from Item 1 to Date; click the attributes inspector Image combo box; and select the clockicon.png image. If you are using your own set of icons, select one of the .png files you provided instead. For the rest of this chapter, we’ll discuss the resources we provided. Make adjustments for your own media, as necessary. If you look over at the Tab Bar Controller window, you’ll see that the leftmost tab bar item now reads Date and has a picture of a clock on it. We now need to repeat this process for the other four tab bar items. The second view controller should be given a title of Single and a nib name of SingleComponentPickerView. In the identity inspector, its class should be changed to SingleComponentPickerViewController. The second tab bar item should be given a title of Single, and it should use the icon called singleicon.png. The third view controller should be given a title of Double and a nib name of DoubleComponentPickerView. Its class should be changed to DoubleComponentPickerViewController. The third tab bar item should be assigned a title of Double, and it should be assigned the image called doubleicon.png. The fourth view controller should be assigned a title of Dependent and a nib name of DependentComponentPickerView. Its class should be changed to DependentComponentPickerViewController. The fourth tab bar item gets a title of Dependent and should be assigned the icon image dependenticon.png. The fifth and last view controller should be assigned a title of Custom, a nib name of CustomPickerView. Its class should be changed to CustomPickerViewController. The last tab bar item also gets a title of Custom and the toolicon.png icon.
147
148
CHAPTER 7: Tab Bars and Pickers
All that we have left to do in this nib file is to c ontrol-drag from the Pickers App Delegate icon to the Tab Bar Controller icon, selecting the rootController outlet. Save your nib, and go back to Xcode. At this point, the tab bar and the content views should all be hooked up and working. Com‑ pile and run, and your application should launch with a toolbar that functions; clicking a tab should select it. There’s nothing in the content views now, so the changes won’t be very dramatic. But if everything went okay, the basic framework for your multiview application is now set up and working, and we can start designing the individual content views.
TIP If your simulator bursts into flames when you click one of the tabs, don’t panic! Most likely, you’ve either missed a step or made a typo. Go back and check all the nib file names; make sure the connections are right; and make sure the class names are all set correctly.
If you want to make double sure everything is working, you can add a different label or some other object to each of the content views and then relaunch the application. If everything is working, you’ll see the content of the different views change as you select different tabs.
Implementing the Date Picker To implement the date picker, we’ll need a single outlet and a single action. The outlet will be used to grab the value from the date picker. The action will be triggered by a but‑ ton and will throw up an alert to show the date value pulled from the picker. S ingle-click DatePickerViewController.h, and add the following code: #import <UIKit/UIKit.h> @interface DatePickerViewController : UIViewController { IBOutlet UIDatePicker *datePicker; } @property (nonatomic, retain) UIDatePicker *datePicker; -(IBAction)buttonPressed; @end
Save this file, and double-click DatePickerView.xib to open the content view for this first tab in Interface Builder. The first thing we need is a date picker, so look for Date Picker in the library (see Figure 7‑12), and drag one over to the View window. If the View window is not open, open it by d ouble-clicking the View icon in the nib’s main window.
CHAPTER 7: Tab Bars and Pickers
igure 7‑12. The Date Picker in the F library
Place the date picker right at the top of the view. It should take up the entire width of your content view and a good portion of the height. Don’t use the blue guidelines for the picker; it’s designed to fit snugly against the edges of the view (see Figure 7‑13).
igure 7‑13. Place all pickers right up F against the edges of the view.
Single-click the date picker if it’s not already selected, and press ⌘1 to bring up the attributes inspector. As you can see (in Figure 7‑14), a number of attributes can be configured for a date picker. You won’t get off this easy with the rest of the pickers, so enjoy it while you can. We’re going to leave most of the values at their defaults, though you should feel free to play with the options when we’re done to see what they do. The one thing we are going to do is limit the range of the picker to reasonable dates. Change the Minimum date value to 1/1/1900, and change the Maximum to 12/31/2200. Next, grab a Round Rect Button from the library, and place it below the date picker. D ouble-click it, and give it a title of Select, and press ⌘2 to switch to the con‑ nections inspector. Drag from the circle next to the Touch Up Inside event over to the File’s Owner icon, and connect to the buttonPressed action. Then control-drag from the File’s Owner icon back to the date picker, and select the datePicker outlet. Save, close the nib, and go back to Xcode.
igure 7‑14. The attributes inspecF tor for a Date Picker
149
150
CHAPTER 7: Tab Bars and Pickers
Now we just need to implement DatePickerViewController, so click DatePickerViewController.m, and add the following code: #import "DatePickerViewController.h" @implementation DatePickerViewController @synthesize datePicker; - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) { // Initialization code } return self; } -(IBAction)buttonPressed { 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]; } - (void)viewDidLoad { NSDate *now = [[NSDate alloc] init]; [datePicker setDate:now animated:YES]; [now 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 }
CHAPTER 7: Tab Bars and Pickers
- (void)dealloc { [datePicker release]; [super dealloc]; }
We added the implementation of buttonPressed, and we overrode viewDidLoad. In buttonPressed, we use our datePicker outlet to get the current date value from the date picker, and then we construct a string based on that date and use it to show an alert sheet. In viewDidLoad, we created a new NSDate object. An NSDate object created this way will hold the current date and time. We then set datePicker to that date, which ensures that every time this view loads, the picker will reset to the current date and time. Go ahead and build and run, make sure your date picker checks out. If everything went OK, your application should look like F igure 7‑2 when it runs. If you click the Select button, an alert sheet will pop up telling you the date and time currently selected in the date picker. Though the date picker does not allow you to specify seconds or a time zone, the alert that displays the selected date and time displays both seconds and a time zone offset. We could have added a formatter to simplify the string displayed in the alert, but isn’t this chapter long enough already?
Implementing the Single Component Picker Well, date pickers are easy enough, but let’s look at using pickers that let the user select from a list of values. In this example, we’re going to create an NSArray to hold the values we want 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 to display. The picker doesn’t really care where the underlying data is. It asks for the data when it needs it, and the data‑ source and delegate work together to supply that data. As a result, the data could be coming from a static list, as we’ll do in this section, or could be loaded from a file or a URL, or even made up or calculated on the fly.
Declaring Outlets and Actions As always, we need to make sure our outlets and actions are in place in our controller’s header file before we start working in Interface Builder. In Xcode, s ingle-click SingleComponentPickerViewController.h. This controller class will act as both the datasource and the delegate for its picker, so we need to make sure it conforms to the protocols for those two roles. In addition, we’ll need to declare an outlet and an action. Add the following code: #import <UIKit/UIKit.h> @interface SingleComponentPickerViewController : UIViewController <UIPickerViewDelegate, UIPickerViewDataSource> {
151
152
CHAPTER 7: Tab Bars and Pickers
IBOutlet NSArray
UIPickerView *singlePicker; *pickerData;
} @property (nonatomic, retain) UIPickerView *singlePicker; @property (nonatomic, retain) NSArray *pickerData; - (IBAction)buttonPressed; @end
We start by conforming our controller class to two protocols, UIPickerViewDelegate and UIPickerViewDataSource. After that, we declare an outlet for the picker and a pointer to an NSArray, which will be used to hold the list of items that will be displayed in the picker. Finally, we declare the action method for the button, just as we did for the date picker.
Building the View Double-click SingleComponentPickerView.xib to open the content view for the second tab in our tab bar. Bring over a Picker View from the library (see Figure 7‑15), and add it to your nib’s View window, placing it snugly into the top of the view as you did with the date picker view. After placing the picker, c ontrol-drag from File’s Owner to the picker view, and select the singlePicker outlet. Next, single-click the picker if it’s not already selected, and press ⌘2 to bring up the connections Figure 7‑15. The Picker View in the inspector. If you look at the connections available for library the picker view, you’ll see that the first two items are DataSource and Delegate. Drag from the circle next to DataSource to the File’s Owner icon. Then drag again from the circle next to Delegate to the File’s Owner icon. Now this picker knows that the instance of the SingleComponentPickerViewController class is its datasource and delegate and will ask it to supply the data to be displayed. In other words, when the picker needs information about the data it is going to display, it asks the SingleComponentPickerViewController instance that controls this view for that information. Drag a Round Rect Button to the view, double-click it, and give it a title of Select. Press return to commit the change. In the connections inspector, drag from the circle next to Touch Up Inside to the File’s Owner icon, selecting the buttonPressed action. Save the nib file, close it, and go back to Xcode.
CHAPTER 7: Tab Bars and Pickers
Implementing the Controller As Datasource and Delegate To make our controller work properly as the picker’s datasource and delegate, we are going to have to implement a few methods that you’ve never seen before. Single-click SingleComponentPickerViewController.m, and add the following code: #import "SingleComponentPickerViewController.h" @implementation SingleComponentPickerViewController @synthesize singlePicker; @synthesize pickerData; - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) { // Initialization code } return self; } - (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]; [alert release]; [title release]; } - (void)viewDidLoad { NSArray *array = [[NSArray alloc] initWithObjects:@"Luke", @"Leia", @"Han", @"Chewbacca", @"Artoo", @"Threepio", @"Lando", nil]; self.pickerData = array; [array release]; } - (BOOL)shouldAutorotateToInterfaceOrientation: (UIInterfaceOrientation)interfaceOrientation { // Return YES for supported orientations return (interfaceOrientation == UIInterfaceOrientationPortrait); }
153
154
CHAPTER 7: Tab Bars and Pickers
- (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
The first two methods should be familiar to you by now. The buttonPressed method is nearly identical to the one we used with the date picker. Unlike the date picker, a regular picker can’t tell us what data it holds, because it doesn’t maintain the data. It hands that job off to the delegate and datasource. Instead, we have to ask the picker which row is selected and then grab the corresponding data from our pickerData array. Here is how we ask it for the selected row: NSInteger row = [singlePicker selectedRowInComponent:0];
CHAPTER 7: Tab Bars and Pickers
Notice that we had to specify which component we want to know about. We only have one component in this picker, so we simply pass in 0, which is the index of the first component.
caution Did you notice that there is no asterisk between NSInteger and row. Although, on the iPhone, the prefix “NS” usually indicates an Objective-C class from the Foundation framework, this is one of the exceptions to that general rule. NSInteger is always defined as an integer datatype, either an int or a long. We use NSInteger rather than int or long, because when we use NSInteger, the compiler automatically chooses whichever size is best for the platform for which we are compiling. It will create a 32-bit int when compiling for a 32-bit processor and a longer 6 4-bit long when compiling for a 64-bit architecture. At present, there is no 64-bit iPhone, but who knows? Someday in the future, there may be. You might also write classes for your iPhone applications that you’ll later want to recycle and use in Cocoa applications for Mac OS X, which already does run on both 32- and 6 4-bit machines.
In viewDidLoad, we create an array with several objects so that we have data to feed the picker. Usually, your data will come from other sources, like a property list in your project’s Resources folder. By embedding a list of items in our code the way we’ve done here, we are making it much harder on ourselves if we need to update this list or if we want to have our application translated into other languages. But this approach is the quickest and easiest way to get data into an array for demonstration purposes. Even though you won’t usually create your arrays like this, you will almost always cache the data you are using into an array here in the viewDidLoad method so that you’re not constantly going to disk or to the net‑ work every time the picker asks you for data.
TIP If you’re not supposed to create arrays from lists of objects in your code as we just did in viewDidLoad, how should you do it? Embed the lists in property list files, and add those files to the Resources folder of your project. Property list files can be changed without recompiling your source code, which means no risk of introducing new bugs when you do so. You can also provide different versions of the list for different languages, as you’ll see in the chapter on localizing your application, Chapter 17. Property lists can be created using the Property List Editor application located at /Developer/Applications/Utilities/Property List Editor.app, or right in Xcode, which supports the editing of property lists in the editor pane. Both NSArray and NSDictionary offer a method called initWithContentsOfFile: to allow you to initialize instances from a property file, something we’ll do in this chapter when implementing the Dependent tab.
155
156
CHAPTER 7: Tab Bars and Pickers
At the bottom of the file, we get into the new methods required to implement the picker. The first two methods after dealloc are from the UIPickerViewDataSource protocol, and they are both required for all pickers (except date pickers). Here’s the first one: - (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView { return 1; }
Pickers can have more than one spinning wheel, or component, and this is how the picker asks how many components it should display. We only want to display one list this time, so we simply return a value of 1. Notice that a UIPickerView is passed in as a parameter. This parameter points to the picker view that is asking us the question, which makes it possible to have multiple pickers being controlled by the same datasource. In our case, 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 is used by the picker to ask how many rows of data there are for a given component: - (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component { return [pickerData count]; }
Once again, we are told which picker view is asking, and which component that picker is asking about. Since we know that we have only one picker and one component, we don’t bother with either of the arguments and simply return the count of objects from our data array. After the two datasource methods, we implement one delegate method. Unlike the data‑ source methods, all of the delegate methods are optional. The term “optional” is a bit deceiving because you do have to implement at least one delegate method. You will usually implement the method that we are implementing here. As you’ll see when we get to the custom picker, if you want to display something other than text in the picker, you have to implement a different method instead. - (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component { return [pickerData objectAtIndex:row]; }
CHAPTER 7: Tab Bars and Pickers
#PRAGMA What? Did you notice these lines of code from SingleComponentPickerViewController.m? #pragma mark #pragma mark Picker Data Source Methods
Any line of code that begins with #pragma is technically a compiler directive, specifically, a pragmatic, or c ompiler-specific, directive that won’t necessarily work with other compilers or in other environments. If the compiler doesn’t recognize the directive, it ignores it, though it may generate a warning. In this case, the #pragma directives are actually directives to the IDE, not the compiler, and they tell Xcode’s editor to put a break in the p op-up menu of methods and functions at the top of the editor pane as shown in the following screen shot. The first one puts a divider line in the menu. The second creates a bold entry.
Some of your classes, especially some of your controller classes, are likely to get rather long, and the methods and functions p op-up menu makes navigating around your code much easier. Putting in #pragma directives and logically organizing your code will make that pop-up more efficient to use.
In this 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 com‑ ponent 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 appropri‑ ate item from our data array.
157