Tab Bars and Pickers - Beginning iPhone Development: Exploring the iOS SDK, Seventh Edition (2014)

Beginning iPhone Development: Exploring the iOS SDK, Seventh Edition (2014)

Chapter 7. Tab Bars and Pickers

In the previous 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 will reinforce a lot of what you learned in Chapter 6. Now, you’re too smart to spend a whole chapter doing stuff you already sort of know how to do, so we’re going to use those five content views to demonstrate a type of iOS control that we have not yet covered. The control is called a picker view, or just a picker.

You may not be familiar with the name, but you’ve almost certainly used a picker if you’ve owned an iPhone or iPod touch for more than, say, 10 minutes. Pickers are the controls with dials that spin. You use them to input dates in the Calendar application or to set a timer in the Clock application (see Figure 7-1). On the iPad, the picker view isn’t quite as common since the larger display lets you present other ways of choosing among multiple items; but even there, it’s used in the Calendar application.

image

Figure 7-1. A picker in the Clock application

Pickers are a bit more complex than the iOS 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.

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 that it has five tabs, add an icon to each of the tab bar items, and then create a series of content views and connect each view to a tab.

The application’s content views will feature five different pickers:

· Date picker: 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 display an alert that shows the date that was picked.

image

Figure 7-2. The first tab will show a date picker

· Single-component picker: The second tab will feature a picker with a single list of values (see Figure 7-3). This picker is a little 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 data source.

image

Figure 7-3. A picker displaying a single list of values

· Multicomponent picker: 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. You’ll see how to use the data source 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.

image

Figure 7-4. A two-component picker, showing an alert that reflects our selection

· Picker with dependent components: In the fourth content view, we’ll build another picker with two components. But this time, the values displayed in the component on the right will 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 Figure 7-5).

image

Figure 7-5. In this picker, one component is dependent on the other. As you select a state in the left component, the right component changes to a list of ZIP codes in that state

· Custom picker with images: Last, but most certainly not least, we’re going to have some fun with the fifth content view. We’ll demonstrate how to add image data to a picker, and we’re going to do it by writing a little game that uses a picker with five components. In several places in Apple’s documentation, the picker’s appearance 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 Figure 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 spin to a new, randomly selected value. If three copies of the same image appear in a row, the user wins.

image

Figure 7-6. Our fifth component picker. Note that we do not condone using your iPhone as a tiny casino

Delegates and Data Sources

Before we dive in and start building our application, let’s look at what makes pickers more complex than the other controls you’ve used so far. With the exception of the date picker, you can’t use a picker by just grabbing one in the object library, dropping it on your content view, and configuring it. You also need to provide each picker with both a picker delegate and a picker data source.

By this point, you should be comfortable using delegates. We’ve already used application 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. The picker gets its data from the delegate.

In addition to the delegate, pickers need to have a data source. The data source tells the picker how many components it will be working with and how many rows make up each component. The data source works like the delegate in that its methods are called at certain, prespecified times. Without a data source and a delegate, pickers cannot do their job; in fact, they won’t even be drawn.

It’s very common for the data source and the delegate to be the same object. And it’s 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 of our application’s content panes will be the data source and the delegate for their picker.

Note Here’s a pop quiz: Is the picker data source part of the model, view, or controller portion of the application? It’s a trick question. A data source sounds like it must be part of the model, but it’s actually part of the controller. The data source isn’t usually an object designed to hold data. In simple applications, the data source might hold data, but its true job is to retrieve data from the model and pass it along to the picker.

Let’s fire up Xcode and get to it.

Creating the Pickers Application

Although Xcode provides 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.

Create a new project, select the Single View Application template again, and choose Next to go to the next screen. In the Product Name field, type Pickers. Make sure the check box that says Use Core Data is unchecked, and set the Language to Objective-C and the Devices pop-up toUniversal. Then choose Next again. Xcode will let you select the folder where you want to save your project.

We’re going to walk you through the process of building the whole application; but at any step of the way, if you feel like challenging yourself by moving ahead, by all means do so. If you get stumped, you can always come back. If you don’t feel like skipping ahead, that’s just fine. We love the company.

Creating the View Controllers

In the previous chapter, we created a root view controller (“root controller” for short) to manage the process of swapping our application’s other views. We’ll be doing that again this time, but we won’t need to create our own root view controller class. Apple provides a very good class for managing tab bar views, so we’re just going to use an instance of UITabBarController as our root controller.

First, we need to create five new classes in Xcode: the five view controllers that the root controller will swap in and out.

Expand the Pickers folder in the Project Navigator. There, you’ll see the source code files that Xcode created to start off the project. Single-click the Pickers folder, and press imageN or select File image New image File. . ..

Select iOS and then Source in the left pane of the new file assistant, and then select the icon for Cocoa Touch Class and click Next to continue. The next screen lets you give your new class a name. Enter DatePickerViewController in the Class field. Ensure that the Subclass of field contains UIViewController. Make sure that the Also create XIB file check box is unchecked, set the Language to Objective-C, and then click Next.

You’ll be shown a folder selection window, which lets you choose where the class should be saved. Choose the Pickers directory, which already contains the AppDelegate class and a few other files. Make sure also that the Group pop-up has the Pickers folder selected and that the target check box for Pickers is checked.

After you click the Create button, two new files will appear in your Pickers folder: DatePickerViewController.h and DatePickerViewController.m.

Repeat those steps four more times, using the names SingleComponentPickerViewController, DoubleComponentPickerViewController, DependentComponentPickerViewController, and CustomPickerViewController. At the end of all this, the Pickers folder should contain all the fresh files, nicely bunched together (see Figure 7-7).

image

Figure 7-7. The Project Navigator should contain all these files after creating the five view controller classes

Creating the Tab Bar Controller

Now, let’s create our tab bar controller. The project template already contains a view controller called ViewController, which is a subclass of UIViewController. To convert it to a tab bar controller, all we need to do is change its base class. Open ViewController.h and make the following change shown in bold:

#import <UIKit/UIKit.h>

@interface ViewController : UITabBarController

Next, we need to set the tab bar controller up in the storyboard, so open Main.storyboard. The template added an initial view controller, which we’re going to replace, so select it in the Document Outline or the editor area and delete it by pressing the Delete key. In the Object Library, locate a Tab Bar Controller and drag it over to the editing area (see Figure 7-8).

image

Figure 7-8. Dragging a tab bar controller from the library into the editor area. That’s one heck of a big thing you’re dragging around there

While you’re dragging, you’ll see that, unlike the other controllers we’ve been asking you to drag out from the object library, this one actually pulls out three complete view-controller pairs at once, all of which are connected to each other with curved lines. This is actually more than just a tab bar controller; it’s also two child controllers, already connected and ready to use.

Once you drop the tab bar controller onto the editing area, three new scenes are added to the storyboard. If you expand the document view on the left, you will see a nice overview of all the scenes contained in the storyboard (see Figure 7-9). You’ll also see the curvy lines still in place connected the tab bar controller with each of its children. Those lines will always adjust themselves to stay connected if you move the scenes around, which you are always free to do. The on-screen position of each scene within a storyboard has no impact on your app’s appearance when it runs.

image

Figure 7-9. The tab bar controller’s scene, and two child scenes. Notice the tab bar containing two tabs at the bottom of the view and the curved lines connected to each of the child view controllers

This tab bar controller will be our root controller. As a reminder, the root controller controls the very first view that the user will see when your program runs. It is responsible for switching the other views in and out. Since we’ll connect each of our views to one of the tabs in the tab bar, the tab bar controller makes a logical choice as a root controller. We need to tell iOS that the tab bar controller is the one that it should load from Main.storyboard when the application starts. To do this, select the Tab Bar Controller icon in the Document Outline and open the Attributes Inspector; and then in the View Controller section, check the Is Initial View Controller check box. With the view controller still selected, switch to the Identity Inspector and change the Class to ViewController.

Tab bars can use icons to represent each of the tabs, so we should also add the icons we’re going to use before editing the storyboard. You can find some suitable icons in the 07 - ImageSets folder of the source code archive for this book. Each subfolder of 07 - ImageSets contains three images (one for devices with a standard display, two for Retina devices). In the Xcode Project Navigator, select Images.xcassets, which already contains default graphics for an icon and a launch image. Next, drag each subfolder from the 07 - ImageSets folder and drop it into the left column of the editing area, underneath AppIcon, to copy them all into the project.

If you want to make your own icons instead, there are some guidelines for how they should be created. The icons you use should be 24 × 24 pixels and saved in .png format. The icon file should have a transparent background. Don’t worry about trying to color the icons so that they match the appearance of the tab bar. Just as it does with the application icon, iOS will take your image and make it look just right.

Tip An image size of 24 × 24 pixels is actually for standard displays; for Retina displays on iPhone 4 and later, and for the new iPad, you need a double-sized image, or it will appear pixelated. For the iPhone 6 Plus, you need to provide an image that’s three times the size of the original. This is very easy: for any image foo.png, you should also provide an image named foo@2x.png that is doubled in size and another called foo@3x.png that is three times the size. Calling [UIImage imageNamed:@"foo"] will return the normal-sized image or the double-sized image automatically to best suit the device your app is currently running on.

Back in the storyboard, you can see that each of the child view controllers shows a name like “Item 1” at the top and has a single bar item at the bottom of its view, with a simple label matching what is present in the tab bar. We might as well set these two up so that they have the right names from the start, so select the Item 1 view controller, and then click the tab bar item at the bottom or in the Document Outline. Open the Attributes Inspector, and you’ll see a text field for setting the Title of the Bar Item, which currently contains the text Item 1. Replace the text with Date and press the Enter key. This immediately changes the text of the bar item at the bottom of this view controller, as well as the corresponding tab bar item in the tab bar controller. While you’re still in the inspector, click the Image pop-up and select clockicon to set the icon, too. Couldn’t be simpler!

Now repeat the same steps for the second child view controller, but name this one Single and use the singleicon image for its bar item.

Our next step is to complete our tab bar controller so it reflects the five tabs shown in Figure 7-2. Each of those five tabs represents one of our five pickers. The way we’re going to do this is by simply adding three more view controllers to the storyboard (in addition to the two that were added along with the tab bar controller), and then connecting each of them so that the tab bar controller can activate them. Get started by dragging out a normal View Controller from the object library. Next, Control-drag from the tab bar controller to your new view controller, release the mouse button, and select view controllers from the Relationship Segue section of the small pop-up window that appears. This tells the tab bar controller that it has a new child to maintain, so the tab bar immediately acquires a new item, and your new view controller gets a bar item in the bottom of its view, just like the others already had. Now do the same steps outlined previously to give this latest view controller’s bar item Double as a title and doubleicon for its image.

Now we are really getting somewhere. Drag out two more view controllers and connect each of them to the tab bar controller as described previously. One at a time, select each of their bar items, naming one of them Dependent with dependenticon as its image, and the other Custom withtoolicon as its image.

Now that all our view controllers are in place, it’s time to set up each of them with the correct controller class. This will let us have different functionality in each of these views. In the Document Outline, select the view controller labeled Item 1 and bring up the Identity Inspector. In theCustom Class section of the inspector, change the class to DatePickerViewController, and press Return or Tab to set it. You’ll see that the name of the selected control in the Document Outline changes to Date, mirroring the change you made.

Now repeat this same process for the next four view controllers, in the order in which they appear at the bottom of the tab bar controller. In the Identity Inspector for each, use the class names SingleComponentPickerViewController, DoubleComponentPickerViewController,DependentComponentPickerViewController, and CustomPickerViewController, respectively.

Before moving on to the next bit of GUI editing, save your storyboard file.

The Initial Test Run

At this point, the tab bar and the content views should all be hooked up and working. Compile and run, and your application should launch with a tab bar that functions (see Figure 7-10). Click each of the tabs in turn. Each tab should be selectable.

image

Figure 7-10. The application with five empty but selectable tabs

There’s nothing in the content views now, so the changes won’t be very dramatic. In fact, you won’t see any difference at all, except for the highlighting tab bar items. But if everything went OK, 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 make sure the connections are right and the class names are all set correctly.

If you want to make doubly 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. At this point, you should 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 button and will put up an alert to show the date value pulled from the picker. We’ll add both of these from inside Interface Builder while editing the Main.storyboard file, so select it in the Project Navigator if it’s not already front-and-center.

The first thing we need to do is find a Date Picker in the Object Library and drag it over to the Date Scene in the editing area. Click the Date icon in the Document Outline to bring the correct view controller to the front, and then drag the date picker from the Object Library and place it at the top of the view, right up against the top of the display. It’s OK if it overlaps the status bar because this control has so much built-in vertical padding at the top that no one will notice.

Now we need to apply Auto Layout constraints so that the date picker is correctly placed when the application runs on any kind of device. We want the picker to be horizontally centered and anchored to the top of the view, so we need two constraints. Click the Align button below the storyboard, check the Horizontal Center in Container box, and then click Add 1 Constraint. Click the Pin button (which is next to the Align button). Using the four distance boxes at the top of the pop-up, set the distance between the picker and the top of edge of the view above it to zero by entering zero in the top box, and then click the dashed red line below it so that it becomes a solid line. At the bottom of the pop-up, set Update Frames to Items of New Constraints, and then click Add 1 Constraint. The date picker will resize and move to its correct position, as shown inFigure 7-11.

image

Figure 7-11. The date picker, positioned at the top of its view controller’s view

Single-click the date picker if it’s not already selected and go back to the Attributes Inspector. As you can see in Figure 7-12, a number of attributes can be configured for a date picker. We’re going to leave most of the values at their defaults (but feel free to play with the options when we’re finished, to see what they do). The one thing we will do is limit the range of the picker to reasonable dates. Look for the heading that says Constraints and check the box that reads Minimum Date. Leave the value at the default of 1/1/1970. Also check the box that reads Maximum Date and set that value to 12/31/2200.

image

Figure 7-12. The Attributes Inspector for a date picker. Set the maximum date, but leave the rest of the settings at their default values

Now let’s connect this picker to its controller. Press imageimageEnter to open the Assistant Editor and make sure the jump bar at the top of the Assistant Editor is set to Automatic. That should make DatePickerViewController.m show up there. Next, Control-drag from the picker to the class extension part of DatePickerViewController.m, between the @interface and @end lines, releasing the mouse button when the Insert Outlet, Action, or Outlet Collection tooltip appears. In the pop-up window that appears after you let go, make sure the Connection is set to Outlet, enterdatePicker as the Name, and then press Enter to create the outlet and connect it to the picker.

Next, grab a Button from the library and place it a small distance below the date picker. Double-click the button and give it a title of Select. We want this button to be horizontally centered and to stay a fixed distance below the date picker. With the button selected, click the Align button at the bottom of the storyboard, check the Horizontal Center in Container box, and click Add 1 Constraint. To fix the distance between them, Control-drag from the button to the date picker and release the mouse. In the pop-up that appears, select Vertical Spacing. Finally, click the Resolve Auto Layout Issues button at the bottom of the storyboard and then click Update Frames in the top section of the pop-up. The button should move to its correct location and there should no longer be any Auto Layout warnings.

Now Control-drag from the button to the source code in the assistant view, this time dragging it down near the bottom, just above the final @end line, until you see the Insert Action tooltip appear. Name the new action buttonPressed and press Enter to connect it. Doing so creates an empty method called buttonPressed:, which you should now complete with the following bold code:

- (IBAction)buttonPressed:(id)sender {
NSDate *date = self.datePicker.date;
NSString *message = [[NSString alloc] initWithFormat:
@"The date and time you selected is %@", date];
UIAlertController *alert =
[UIAlertController alertControllerWithTitle:
@"Date and Time Selected"
message:message
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *action =
[UIAlertAction actionWithTitle:@"That's so true!"
style:UIAlertActionStyleDefault handler:nil];
[alert addAction:action];
[self presentViewController:alert animated:YES completion:nil];
}

Here, 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.

Next, add a bit of setup code to the viewDidLoad: method to finish this controller class:

- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
NSDate *now = [NSDate date];
[self.datePicker setDate:now animated:NO];
}

In viewDidLoad, we create 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 is loaded from the storyboard, the picker will reset to the current date and time.

Go ahead and build and run to make sure your date picker checks out. If everything went OK, your application should look like Figure 7-2 when it runs. If you choose the Select button, an alert will pop up, telling you the date and time currently selected in the date picker.

Note The date picker does not allow you to specify seconds or a time zone. The alert displays the time with seconds and in Greenwich Mean Time (GMT). We could have added some code to simplify the string displayed in the alert, but isn’t this chapter long enough already? If you’re interested in customizing the formatting of the date, take a look at the NSDateFormatter class.

Implementing the Single-Component Picker

Our next picker lets the user select from a list of values. In this example, we’re going to use 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 data source and delegate to get the data they need to display. The picker doesn’t really care where the underlying data lives. It asks for the data when it needs it, and the data source and delegate (which are often, in practice, the same object) 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. It also could be loaded from a file or a URL, or even made up or calculated on the fly.

For the picker class to ask its controller for data, we must ensure that the controller implements the right methods. One part of doing that is declaring in the controller’s interface that it will implement a couple of protocols. In the Project Navigator, single-clickSingleComponentPickerViewController.h. This controller class will act as both the data source and the delegate for its picker, so we need to make sure it conforms to the protocols for those two roles. Add the following code:

#import <UIKit/UIKit.h>

@interface SingleComponentPickerViewController : UIViewController
<UIPickerViewDelegate, UIPickerViewDataSource>

@end

Building the View

Now select Main.storyboard again, since it’s time to edit the content view for the second tab in our tab bar. In the Document Outline, click the Single icon to bring the view controller into the foreground in the editor area. Next, bring over a Picker View from the library (see Figure 7-13) and add it to your view, placing it snugly into the top of the view, as you did with the date picker view.

image

Figure 7-13. Adding a picker view from the library to your second view

The picker needs to be horizontally centered and pinned to the top of the scene. You can do this by adding the same Auto Layout constraints to the picker that you added to the Date Picker in the previous example. If you can’t remember how to do that, refer back to the instructions in the “Implementing the Date Picker” section. We’re going to be using these constraints again and again in this chapter, so it’s worth remembering how to create them, or writing them down.

Now let’s connect this picker to its controller. The procedure here is just like for the previous picker view: open the Assistant Editor, set the jump bar to show the .m file, Control-drag from the picker to the @interface section at the top of SingleComponentPickerViewController.m, and create an outlet named singlePicker.

Next, with the picker selected, press imageimage6 to bring up the Connections Inspector. If you look at the connections available for the picker view, you’ll see that the first two items are dataSource and delegate. If you don’t see those outlets, make sure you have the picker selected, rather than theUIView that contains it! Drag from the circle next to dataSource to the View Controller icon at the top of the scene in the storyboard or in the Document Outline, and then drag from the circle next to delegate to the View Controller icon. Now this picker knows that the instance of theSingleComponentPickerViewController class in the storyboard is its data source and delegate, and the picker 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 theSingleComponentPickerViewController instance that controls this view for that information.

Drag a Button to the view, place it just below the picker. Double-click the button and give it the title Select. Press Return to commit the change. In the Connections Inspector, drag from the circle next to Touch Up Inside to code in the assistant view, releasing it just above the @end at the bottom to make a new action method. Name this action buttonPressed and you’ll see that Xcode fills in an empty method.

As always when we add a view to a storyboard, we need to set its Auto Layout constraints. In the case of the button, these constraints need to center it horizontally and make sure its distance below the picker remains fixed. You saw how to do this when we added a similar button to the Data Picker scene, so just use the same constraints here. Now you’ve finished building the GUI for the second tab. Save the storyboard and let’s get back to some coding.

Implementing the Controller As a Data Source and Delegate

To make our controller work properly as the picker’s data source and delegate, we’ll start with some code you should feel comfortable with, and then add a few methods that you’ve never seen before.

Single-click SingleComponentPickerViewController.m in the Project Navigator and add the following property to the @interface section at the top. This will let us keep a pointer to an array with the names of several well-known movie characters:

@interface SingleComponentPickerViewController ()

@property (weak, nonatomic) IBOutlet UIPickerView *singlePicker;
@property (strong, nonatomic) NSArray *characterNames;

@end

Next, add this initialization code to the viewDidLoad method to set up the contents of the character name array:

- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.characterNames = @[@"Luke", @"Leia", @"Han", @"Chewbacca",
@"Artoo", @"Threepio", @"Lando"];
}

And then, add the following code to the buttonPressed: method:

- (IBAction)buttonPressed:(id)sender {
NSInteger row = [self.singlePicker selectedRowInComponent:0];
NSString *selected = self.characterNames[row];
NSString *title = [[NSString alloc] initWithFormat:
@"You selected %@!", selected];

UIAlertController *alert =
[UIAlertController alertControllerWithTitle:title
message:@"Thank you for choosing."
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *action =
[UIAlertAction actionWithTitle:@"You're welcome"
style:UIAlertActionStyleDefault handler:nil];
[alert addAction:action];
[self presentViewController:alert animated:YES completion:nil];
}

These two methods should be familiar to you by now. The buttonPressed: method is nearly identical to the one we used with the date picker, but unlike the date picker, a regular picker can’t tell us what data it holds because it doesn’t maintain the data. It hands off that job to the delegate and data source. Instead, the buttonPressed: method needs to ask the picker which row is selected, and then grabs the corresponding data from your pickerData array. Here is how we ask it for the selected row:

NSInteger row = [self.singlePicker selectedRowInComponent:0];

Notice that we needed to specify which component we want to know about. We have only one component in this picker, so we simply pass in 0, which is the index of the first component.

Note Did you notice that there is no asterisk between NSInteger and row in our request for the selected row? Throughout most of the iOS SDK, the prefix NS often indicates an Objective-C class from the Foundation framework, but 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, with 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 64-bit long when compiling for a 64-bit architecture. Now that Apple has begun releasing 64-bit iOS devices, using these types makes a lot of sense. You might also write classes for your iOS applications that you’ll later want to recycle and use in Cocoa applications for OS X, which has been running on both 32- and 64-bit machines for several years.

In viewDidLoad, we assign an array with several objects to the characterNames property 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 or a web service query. 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 configure some form of access to your application’s model objects here in the viewDidLoad method, so that you’re not constantly going to disk or to the network 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 there is little 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 Chapter 22. Property lists can be created directly in Xcode, which offers a template for creating one in the Resource section of the new file assistant and 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 list file, as we’ll do later in this chapter when we implement the Dependent tab. Property lists are discussed in more detail in Chapter 13.

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 [self.characterNames count];
}

#pragma mark Picker Delegate Methods
- (NSString *)pickerView:(UIPickerView *)pickerView
titleForRow:(NSInteger)row
forComponent:(NSInteger)component {
return self.characterNames[row];
}

@end

These three methods are required to implement the picker. The first two methods 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 want to display only one list this time, so we 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 data source. 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 data source 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 [self.characterNames 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 sole data array.

#PRAGMA WHAT?

Did you notice the following 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. More specifically, a #pragma marks a pragmatic, or compiler-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 pop-up menu of methods and functions at the top of the editor pane. The first one puts the break in the menu. The second creates a text entry containing whatever the rest of the line holds, which you can use as a sort of descriptive header for groups of methods in your source code.

Some of your classes, especially some of your controller classes, are likely to get rather long, and the methods and functions pop-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.

After the two data source 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 need to implement at least one delegate method. You will usually implement the method that we are implementing here. However, if you want to display something other than text in the picker, you must implement a different method instead, as you’ll see when we get to the custom picker later in this chapter:

#pragma mark Picker Delegate Methods
- (NSString *)pickerView:(UIPickerView *)pickerView
titleForRow:(NSInteger)row
forComponent:(NSInteger)component {
return self.characterNames[row];
}

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 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.

Go ahead and compile and run again. When the simulator comes up, switch to the second tab—the one labeled Single—and check out your new custom picker, which should look like Figure 7-3.

When you’re done reliving all those Star Wars memories, come on back to Xcode and we’ll show you how to implement a picker with two components. If you feel up to a challenge, this next content view is actually a good one for you to attempt on your own. You’ve already seen all the methods you’ll need for this picker, so go ahead and take a crack at it. We’ll wait here. You might want to start with a good look at Figure 7-4, just to refresh your memory. When you’re finished, read on and you’ll see how we tackled this problem.

Implementing a Multicomponent Picker

The next tab will have a picker with two components, or wheels, each independent of the other. The left wheel will have a list of sandwich fillings and the right wheel will have a selection of bread types. We’ll write the same data source and delegate methods that we did for the single-component picker. We’ll just need to write a little additional code in some of those methods to make sure we’re returning the correct value and row count for each component.

Declaring Outlets and Actions

Single-click DoubleComponentPickerViewController.h and add the following code:

#import <UIKit/UIKit.h>

@interface DoubleComponentPickerViewController : UIViewController
<UIPickerViewDelegate, UIPickerViewDataSource>

@end

Here, we simply conform our controller class to both the delegate and data source. Save this and click Main.storyboard to work on the GUI.

Building the View

Select the Double Scene in the Document Outline and click its View Controller icon to bring the view controller to the front in the editor area. Now add a picker view and a button to the view, change the button label to Select, and then make the necessary connections. We’re not going to walk you through it this time, but you can refer to the previous section if you need a step-by-step guide, since the two view controllers are identical in terms of connections in the storyboard. Here’s a summary of what you need to do:

1. Create an outlet called doublePicker in the class extension of the DoubleComponentPickerViewController class to connect the view controller to the picker.

2. Connect the dataSource and delegate connections on the picker view to the view controller (use the Connections Inspector).

3. Connect the Touch Up Inside event of the button to a new action called buttonPressed on the view controller (use the Connections Inspector).

4. Add Auto Layout constraints to the picker and the button to pin them in place.

Make sure you save your storyboard before you dive back into the code. Oh, and dog-ear this page (or use a bookmark, if you prefer). You’ll be referring to it in a bit.

Implementing the Controller

Select DoubleComponentPickerViewController.m and add the following code at the top of the file:

#import "DoubleComponentPickerViewController.h"

#define kFillingComponent 0
#define kBreadComponent 1

@interface DoubleComponentPickerViewController ()

@property (weak, nonatomic) IBOutlet UIPickerView *doublePicker;
@property (strong, nonatomic) NSArray *fillingTypes;
@property (strong, nonatomic) NSArray *breadTypes;

@end

As you can see, we start out by defining two constants that will represent the two components, which is just to make our code easier to read. Picker components are referred to by number, with the leftmost component being assigned zero and increasing by one each move to the right. Next, we declare properties for two arrays to hold the data for our two picker components.

Now implement the buttonPressed: method, as shown here:

- (IBAction)buttonPressed:(id)sender {
NSInteger fillingRow = [self.doublePicker selectedRowInComponent:
kFillingComponent];
NSInteger breadRow = [self.doublePicker selectedRowInComponent:
kBreadComponent];

NSString *filling = self.fillingTypes[fillingRow];
NSString *bread = self.breadTypes[breadRow];

NSString *message = [[NSString alloc] initWithFormat:
@"Your %@ on %@ bread will be right up.", filling, bread];

UIAlertController *alert =
[UIAlertController
alertControllerWithTitle:@"Thank you for your order"
message:message
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *action = [UIAlertAction actionWithTitle:@"Great!"
style:UIAlertActionStyleDefault handler:nil];
[alert addAction:action];
[self presentViewController:alert animated:YES completion:nil];
}

Next, add the following lines of code to the viewDidload method:

- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.fillingTypes = @[@"Ham", @"Turkey", @"Peanut Butter",
@"Tuna Salad", @"Chicken Salad",
@"Roast Beef", @"Vegemite"];
self.breadTypes = @[@"White", @"Whole Wheat", @"Rye",
@"Sourdough", @"Seven Grain"];
}

Also, add the delegate and data source methods at the bottom, before the final @end line:

#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];
} else {
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[row];
} else {
return self.fillingTypes[row];
}
}

@end

The buttonPressed: method is a bit more involved this time, but there’s very little there that’s new to you. We just need to specify which component we are talking about when we request the selected row using those constants we defined earlier, kBreadComponent andkFillingComponent:

NSInteger fillingRow = [self.doublePicker selectedRowInComponent:
kFillingComponent];
NSInteger breadRow = [self.doublePicker selectedRowInComponent:
kBreadComponent];

You can see here that using the two constants instead of 0 and 1 makes our code considerably more readable. From this point on, the buttonPressed: method is fundamentally the same as the last one we wrote.

viewDidLoad is also very similar to the version we wrote for the previous picker. The only difference is that we are loading two arrays with data rather than just one array. Again, we’re just creating arrays from a hard-coded list of strings—something you generally won’t do in your own applications.

When we get down to the data source methods, that’s where things start to change a bit. In the first method, we specify that our picker should have two components rather than just one:

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

This time, when we are asked for the number of rows, we need to check which component the picker is asking about and return the correct row count for the corresponding array:

- (NSInteger)pickerView:(UIPickerView *)pickerView
numberOfRowsInComponent:(NSInteger)component {
if (component == kBreadComponent) {
return [self.breadTypes count];
} else {
return [self.fillingTypes count];
}
}

Next, in our delegate method, we do the same thing. We check the component and use the correct array for the requested component to fetch and return the correct value:

- (NSString *)pickerView:(UIPickerView *)pickerView
titleForRow:(NSInteger)row
forComponent:(NSInteger)component {
if (component == kBreadComponent) {
return self.breadTypes[row];
} else {
return self.fillingTypes[row];
}
}

That wasn’t so hard, was it? Compile and run your application, and make sure the Double content pane looks like Figure 7-4.

Notice that the wheels are completely independent of each other. Turning one has no effect on the other. That’s appropriate in this case, but there will be times when one component is dependent on another. A good example of this is in the date picker. When you change the month, the dial that shows the number of days in the month may need to change, because not all months have the same number of days. Implementing this isn’t really hard once you know how, but it’s not the easiest thing to figure out on your own, so let’s do that next.

Implementing Dependent Components

We’re picking up steam now. For this next section, we’re not going to hold your hand quite as much when it comes to material we’ve already covered. Instead, we’ll focus on the new stuff. Our new picker will display a list of US states in the left component and a list of corresponding ZIP codes in the right component.

We’ll need a separate list of ZIP code values for each item in the left-hand component. We’ll declare two arrays, one for each component, as we did last time. We’ll also need an NSDictionary. In the dictionary, we’re going to store an NSArray for each state (see Figure 7-14). Later, we’ll implement a delegate method that will notify us when the picker’s selection changes. If the value on the left changes, we will grab the correct array out of the dictionary and assign it to the array being used for the right-hand component. Don’t worry if you didn’t catch all that; we’ll talk about it more as we get into the code.

image

Figure 7-14. Our application’s data. For each state, there will be one entry in a dictionary with the name of the state as the key. Stored under that key will be an NSArray instance containing all the ZIP codes from that state

Add the following code to your DependentComponentPickerViewController.h file:

#import <UIKit/UIKit.h>

@interface DependentComponentPickerViewController : UIViewController
<UIPickerViewDelegate, UIPickerViewDataSource>

@end

Next, add the following to DependentComponentPickerViewController.m:

#import "DependentComponentPickerViewController.h"

#define kStateComponent 0
#define kZipComponent 1

@interface DependentComponentPickerViewController ()

@property (strong, nonatomic) NSDictionary *stateZips;
@property (strong, nonatomic) NSArray *states;
@property (strong, nonatomic) NSArray *zips;

@end

Now it’s time to build the content view. That process will be almost identical to the previous two component views we built. If you get lost, flip back to the “Building the View” section for the single-component picker and follow those step-by-step instructions. Here’s a hint: start off by opening Main.storyboard, find the view controller for the DependentComponentPickerViewController class, and then repeat the same basic steps you’ve done for all the other content views in this chapter. You should end up with an outlet property called dependentPickerconnected to a picker, an empty buttonPressed: method connected to a button, and both the delegate and dataSource properties of the picker connected to the view controller. Don’t forget to add the Auto Layout constraints to both views! When you’re finished, save the storyboard.

OK, take a deep breath. Let’s implement this controller class. This implementation may seem a little gnarly at first. By making one component dependent on the other, we have added a whole new level of complexity to our controller class. Although the picker displays only two lists at a time,our controller class must know about and manage 51 lists. The technique we’re going to use here actually simplifies that process. The data source methods look almost identical to the one we implemented for the DoublePickerViewController. All of the additional complexity is handled elsewhere, between viewDidLoad and a new delegate method called pickerView:didSelectRow:inComponent:.

Before we write the code, we need some data to display. Up till now, we’ve created arrays in code by specifying a list of strings. Because we didn’t want you to need to type in several thousand values, and because we figured we should show you the correct way to do this, we’re going to load the data from a property list. As mentioned, both NSArray and NSDictionary objects can be created from property lists. We’ve included a property list called statedictionary.plist in the project archive, under the 07 – Picker Data folder.

Drag that file into the Pickers folder in your Xcode project. If you single-click the .plist file in the Project Navigator, you can see and even edit the data that it contains (see Figure 7-15).

image

Figure 7-15. The statedictionary.plist file, showing our list of states. Within Ohio, you can see the start of a list of ZIP codes

Now, let’s write some code. In DependentComponentPickerViewController.m, we’re going to first show you some whole methods to implement, and then we’ll break it down into more digestible chunks. Start with the implementation of buttonPressed::

- (IBAction)buttonPressed:(id)sender {
NSInteger stateRow = [self.dependentPicker
selectedRowInComponent:kStateComponent];
NSInteger zipRow = [self.dependentPicker
selectedRowInComponent:kZipComponent];

NSString *state = self.states[stateRow];
NSString *zip = self.zips[zipRow];

NSString *title = [[NSString alloc] initWithFormat:
@"You selected zip code %@.", zip];
NSString *message = [[NSString alloc] initWithFormat:
@"%@ is in %@", zip, state];

UIAlertController *alert =
[UIAlertController alertControllerWithTitle:title
message:message
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *action = [UIAlertAction actionWithTitle:@"OK"
style:UIAlertActionStyleDefault handler:nil];
[alert addAction:action];
[self presentViewController:alert animated:YES completion:nil];
}

Next, add the following code to the existing viewDidLoad method:

- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
NSBundle *bundle = [NSBundle mainBundle];
NSURL *plistURL = [bundle URLForResource:@"statedictionary"
withExtension:@"plist"];

self.stateZips = [NSDictionary
dictionaryWithContentsOfURL:plistURL];

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

NSString *selectedState = self.states[0];
self.zips = self.stateZips[selectedState];
}

And, finally, add the delegate and data source methods at the bottom of the file:

#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];
} else {
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[row];
} else {
return self.zips[row];
}
}

- (void)pickerView:(UIPickerView *)pickerView
didSelectRow:(NSInteger)row
inComponent:(NSInteger)component {
if (component == kStateComponent) {
NSString *selectedState = self.states[row];
self.zips = self.stateZips[selectedState];
[self.dependentPicker reloadComponent:kZipComponent];
[self.dependentPicker selectRow:0
inComponent:kZipComponent
animated:YES];
}
}

@end

There’s no need to talk about the buttonPressed: method since it’s fundamentally the same as the previous one. We should talk about the viewDidLoad method, though. There’s some stuff going on there that you need to understand, so pull up a chair and let’s chat.

The first thing we do in this new viewDidLoad method is grab a reference to our application’s main bundle:

NSBundle *bundle = [NSBundle mainBundle];

What is a bundle, you ask? Well, a bundle is just a special type of folder, the contents of which follow a specific structure. Applications and frameworks are both bundles, and this call returns a bundle object that represents our application.

One of the primary uses of NSBundle is to get to resources that you added to the Resources folder of your project. Those files will be copied into your application’s bundle when you build your application. We’ve added resources like images to our projects; but up to now, we’ve used those only in Interface Builder. If we want to get to those resources in our code, we usually need to use NSBundle. We use the main bundle to retrieve the URL of the resource in which we’re interested:

NSURL *plistURL = [bundle URLForResource:@"statedictionary"
withExtension:@"plist"];

This will return a URL containing the location of the statedictionary.plist file. We can then use that URL to create an NSDictionary object. Once we do that, the entire contents of that property list will be loaded into the newly created NSDictionary object; that is, it is assigned tostateZips:

self.stateZips = [NSDictionary
dictionaryWithContentsOfURL:plistURL];

The dictionary we just loaded uses the names of the states as the keys and contains an NSArray with all the ZIP codes for that state as the values. To populate the array for the left-hand component, we get the list of all keys from our dictionary and assign those to the states array. Before we assign it, though, we sort it alphabetically:

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

Unless we specifically set the selection to another value, pickers start with the first row (row 0) selected. To get the zips array that corresponds to the first row in the states array, we grab the object from the states array that’s at index 0. That will return the name of the state that will be selected at launch time. We then use that state name to grab the array of ZIP codes for that state, which we assign to the zips array that will be used to feed data to the right-hand component:

NSString *selectedState = self.states[0];
self.zips = self.stateZips[selectedState];

The two data source methods are practically identical to the previous version. We return the number of rows in the appropriate array. The same is true for the first delegate method we implemented. The second delegate method is the new one, and it’s where the magic happens:

- (void)pickerView:(UIPickerView *)pickerView
didSelectRow:(NSInteger)row
inComponent:(NSInteger)component {
if (component == kStateComponent) {
NSString *selectedState = self.states[row];
self.zips = self.stateZips[selectedState];
[self.dependentPicker reloadComponent:kZipComponent];
[self.dependentPicker selectRow:0
inComponent:kZipComponent
animated:YES];
}
}

In this method, which is called any time 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 that corresponds to the new selection and assign it to the zips array. Next, we set the right-hand component back to the first row and tell it to reload itself. By swapping the zips array whenever the state changes, the rest of the code remains pretty much the same as it was in the DoublePicker example.

We’re not quite finished yet. Compile and run your application, and then check out the Dependent tab (see Figure 7-16). Do you see anything there you don’t like?

image

Figure 7-16. Do we really want the two components to be of equal size? Notice the clipping of a long state name

The two components are equal in size. Even though the ZIP code will never be more than five characters long, it has been given equal billing with the state. Since state names like Mississippi and Massachusetts won’t fit in half of the picker on the screens of the iPhone 4s, iPhone 5, and iPhone 5s, this seems less than ideal. Fortunately, there’s another delegate method we can implement to indicate how wide each component should be. Add the following method to the delegate section of DependentComponentPickerViewController.m:

- (CGFloat)pickerView:(UIPickerView *)pickerView
widthForComponent:(NSInteger)component {
CGFloat pickerWidth = pickerView.bounds.size.width;
if (component == kZipComponent) {
return pickerWidth/3;
} else {
return 2 * pickerWidth/3;
}
}

In this method, we return a number that represents how many pixels wide each component should be, and the picker will do its best to accommodate this. We’ve chosen to give the state component two-thirds of the available width and the rest goes to the ZIP component. Feel free to experiment with other values to see how the distribution of space between the components changes as you modify them. Save, compile, and run, and the picker on the Dependent tab will look more like the one shown in Figure 7-5.

By this point, you should be pretty darn comfortable with both pickers and tab bar applications. We have one more thing to show you about pickers, and we plan to have a little fun while doing it. Let’s create a simple slot machine game.

Creating a Simple Game with a Custom Picker

Next up, we’re going to create an actual working slot machine. Well, OK, it won’t dispense silver dollars, but it does look pretty cool. Take a look back at Figure 7-6 before proceeding, so you know what we’re building.

Writing the Controller Header File

Begin by adding the following code to CustomPickerViewController.h:

#import <UIKit/UIKit.h>

@interface CustomPickerViewController : UIViewController
<UIPickerViewDataSource, UIPickerViewDelegate>

@end

Next, switch to CustomerPickerViewController.m and add the following property to the class extension near the top of the file:

#import "CustomPickerViewController.h"

@interface CustomPickerViewController ()

@property (strong, nonatomic) NSArray *images;

@end

At this point, all we’ve added to the class is a property for an NSArray object that will hold the images to use for the symbols on the spinners of the slot machine. The rest will come a little later.

Building the View

Even though the picker in Figure 7-6 looks quite a bit fancier than the other ones we’ve built, there’s actually very little difference in the way we’ll design our nib. All the extra work is done in the delegate methods of our controller.

Make sure you’ve saved your new source code, and then select Main.storyboard in the Project Navigator and select the Custom Scene to edit the GUI. Add a picker view, a label below that, and a button below that. Give the button the title Spin.

With the label selected, bring up the Attributes Inspector. Set the Alignment to centered. Then click Text Color and set the color to something bright. Next, let’s make the text a little bigger. Look for the Font setting in the inspector, and click the icon inside it (it looks like the letter T inside a little box) to pop up the font selector. This control lets you switch from the device’s standard system font to another if you like, or simply change the size. For now, just change the size to 48 and delete the word Label, since we don’t want any text displayed until the first time the user wins.

Now add Auto Layout constraints to center the label and button horizontally and to fix the vertical gaps between them and between the label and the picker. You’ll probably find it easiest to drag from the label in the Document Outline when adding its Auto Layout constraints, because the label on the storyboard is empty and so very difficult to find!

After that, make all the connections to outlets and actions. Create a new outlet called picker to connect the view controller to the picker view, another called winLabel to connect the view controller to the label. Again, you’ll find it easiest to use the label in the Document Outline than the one on the storyboard. Next, connect the button’s Touch Up Inside event to a new action method called spin:. After that, just make sure to connect the delegate and data source for the picker.

Oh, and there’s one additional thing that you need to do. Select the picker and bring up the Attributes Inspector. You need to uncheck the check box labeled User Interaction Enabled within the View settings, so that the user can’t manually change the dial and cheat. Once you’ve done all that, save the changes you’ve made to the storyboard.

Fonts Supported by iOS Devices

Be careful when using the fonts palette in Interface Builder for designing iOS interfaces. The Attribute Inspector’s font selector will let you assign from a wide range of fonts, but not all iOS devices have the same set of fonts available. At the time of writing, for instance, there are several fonts that are available on the iPad, but not on the iPhone or iPod touch. You should limit your font selections to one of the font families found on the iOS device you are targeting. This post on Jeff LaMarche’s excellent iOS blog shows you how to grab this list programmatically:http://iphonedevelopment.blogspot.com/2010/08/fonts-and-font-families.html.

In a nutshell, create a view-based application and add this code to the method application:didFinishLaunchingWithOptions: in the application delegate:

for (NSString *family in [UIFont familyNames]) {
NSLog(@"%@", family);
for (NSString *font in [UIFont fontNamesForFamilyName:family]) {
NSLog(@"\t%@", font);
}
}

Run the project in the appropriate simulator, and your fonts will be displayed in the project’s console log.

Implementing the Controller

We have a bunch of new stuff to cover in the implementation of this controller. Select CustomPickerViewController.m and get started by filling in the contents of the spin: method:

- (IBAction)spin:(id)sender {
BOOL win = NO;
int numInRow = 1;
int lastVal = -1;
for (int i = 0; i < 5; i++) {
int newValue = arc4random_uniform((uint)[self.images count]);

if (newValue == lastVal) {
numInRow++;
} else {
numInRow = 1;
}
lastVal = newValue;

[self.picker selectRow:newValue inComponent:i animated:YES];
[self.picker reloadComponent:i];
if (numInRow >=3) {
win = YES;
}
}
if (win) {
self.winLabel.text = @"WINNER!";
} else {
self.winLabel.text = @" "; // Note ths space between the quotes
}
}

Next, insert the following code into the viewDidLoad method:

- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.images = @[[UIImage imageNamed:@"seven"],
[UIImage imageNamed:@"bar"],
[UIImage imageNamed:@"crown"],
[UIImage imageNamed:@"cherry"],
[UIImage imageNamed:@"lemon"],
[UIImage imageNamed:@"apple"]];
self.winLabel.text = @" "; // Note ths space between the quotes
}

Finally, add the following code to the end of the file, before the final @end line:

#pragma mark -
#pragma mark Picker Data Source Methods
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView {
return 5;
}

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

#pragma mark Picker Delegate Methods
- (UIView *)pickerView:(UIPickerView *)pickerView
viewForRow:(NSInteger)row
forComponent:(NSInteger)component reusingView:(UIView *)view {
UIImage *image = self.images[row];
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
return imageView;
}

- (CGFloat)pickerView:(UIPickerView *)pickerView
rowHeightForComponent:(NSInteger)component {
return 64;
}

@end

There’s a lot going on here, huh? Let’s take the new stuff, method by method.

The spin Method

The spin method fires when the user touches the Spin button. In it, we first declare a few variables that will help us keep track of whether the user has won. We’ll use win to keep track of whether we’ve found three in a row by setting it to YES if we have. We’ll use numInRow to keep track of how many of the same value we have in a row so far, and we will keep track of the previous component’s value in lastVal, so that we have a way to compare the current value to the previous value. We initialize lastVal to -1 because we know that value won’t match any of the real values:

BOOL win = NO;
int numInRow = 1;
int lastVal = -1;

Next, we loop through all five components and set each one to a new, randomly generated row selection. We get the count from the images array to do that, which is a shortcut we can use because we know that all five columns use the same number of images:

for (int i = 0; i < 5; i++) {
int newValue = arc4random_uniform((uint)[self.images count])

We compare the new value to the previous value and increment numInRow if it matches. If the value didn’t match, we reset numInRow back to 1. We then assign the new value to lastVal, so we’ll have it to compare the next time through the loop:

if (newValue == lastVal) {
numInRow++;
} else {
numInRow = 1;
}
lastVal = newValue;

After that, we set the corresponding component to the new value, telling it to animate the change, and we tell the picker to reload that component:

[self.picker selectRow:newValue inComponent:i animated:YES];
[self.picker reloadComponent:i];

The last thing we do each time through the loop is check whether we have three in a row, and then set win to YES if we do:

if (numInRow >=3) {
win = YES;
}

Once we’re finished with the loop, we set the label to say whether the spin was a win:

if (win) {
self.winLabel.text = @"WINNER!";
} else {
self.winLabel.text = @" "; // Note ths space between the quotes
}

The viewDidLoad Method

Looking back at what we added here, the first thing was to load six different images, which we added to Images.xcassets right back at the beginning of the chapter. We did this using the imageNamed: convenience method of the UIImage class:

self.images = @[[UIImage imageNamed:@"seven"],
[UIImage imageNamed:@"bar"], [UIImage imageNamed:@"crown"],
[UIImage imageNamed:@"cherry"], [UIImage imageNamed:@"lemon"],
[UIImage imageNamed:@"apple"]];

The last thing we did in this method was to make sure the label contains exactly one space. We want the label to be empty, but if we really make it empty, it collapses to zero height. By including a space, we make sure the label is shown at its correct height:

self.winLabel.text = @" "; // Note ths space between the quotes

That was really simple, wasn’t it? But, um, what do we do with those six images? If you scroll down through the code you just typed, you’ll see that two data source methods look pretty much the same as before; however, if you look further into the delegate methods, you’ll see that we’re using a completely different delegate method to provide data to the picker. The one that we’ve used up to now returned an NSString *, but this one returns a UIView *.

Using this method instead, we can supply the picker with anything that can be drawn into a UIView. Of course, there are limitations on what will work here and look good at the same time, given the small size of the picker. But this method gives us a lot more freedom in what we display, although it is a bit more work:

- (UIView *)pickerView:(UIPickerView *)pickerView
viewForRow:(NSInteger)row
forComponent:(NSInteger)component reusingView:(UIView *)view {
UIImage *image = self.images[row];
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
return imageView;
}

This method returns one UIImageView object initialized with one of the images for the symbols. To do that, we first get the image for the symbol for the row. Next, create and return an image view with that symbol. For views more complex than a single image, it can be beneficial to create all needed views first (e.g., in viewDidLoad), and then return these pre-created views to the picker view when requested. But for our simple case, creating the needed views on the fly works well.

Wow, take a deep breath. You got through all of it in one piece, and now you get to take it for a spin. So, build and run the application and have fun!

Final Details

Our game is rather fun, especially when you think about how little effort it took to build it. Now let’s improve it with a couple more tweaks. There are two things about this game right now that really bug us:

· It’s so darn quiet. Slot machines aren’t quiet!

· It tells us that we’ve won before the dials have finished spinning, which is a minor thing, but it does tend to eliminate the anticipation. To see this in action, run your application again. It is subtle, but the label really does appear before the wheels finish spinning.

The 07 - Picker Sounds folder in the project archive that accompanies the book contains two sound files: crunch.wav and win.wav. Drag both of these files to your project’s Pickers folder. These are the sounds we’ll play when the users tap the Spin button and when they win, respectively.

To work with sounds, we’ll need access to the iOS Audio Toolbox classes. Insert the following line shown in bold above the existing #import line at the top of CustomPickerViewController.m:

#import <AudioToolbox/AudioToolbox.h>
#import "CustomPickerViewController.h"

Next, we need to add an outlet that will point to the button. While the wheels are spinning, we’re going to hide the button. We don’t want users tapping the button again until the current spin is all done. Add the following bold line of code to CustomPickerViewController.m:

@interface CustomPickerViewController ()

@property (strong, nonatomic) NSArray *images;
@property (weak, nonatomic) IBOutlet UIPickerView *picker;
@property (weak, nonatomic) IBOutlet UILabel *winLabel;
@property (weak, nonatomic) IBOutlet UIButton *button;

@end

After you type that and save the file, click Main.storyboard to edit the GUI. Once it’s open, Control-drag from the Custom icon below the Custom Scene in the Document Outline to the Spin button and connect it to the new button outlet we just created. Save the storyboard.

Now, we need to do a few things in the implementation of our controller class. First, we need some instance variables to hold references to the loaded sounds. Open CustomPickerViewController.m and add the following new properties:

@interface CustomPickerViewController ()

@property (strong, nonatomic) NSArray *images;
@property (weak, nonatomic) IBOutlet UIPickerView *picker;
@property (weak, nonatomic) IBOutlet UILabel *winLabel;
@property (weak, nonatomic) IBOutlet UIButton *button;
@property (assign, nonatomic) SystemSoundID winSoundID;
@property (assign, nonatomic) SystemSoundID crunchSoundID;

@end

We also need a couple of methods added to our controller class. Add the following two methods to CustomPickerViewController.m:

- (void)showButton {
self.button.hidden = NO;
}

- (void)playWinSound {
if (_winSoundID == 0) {
NSURL *soundURL = [[NSBundle mainBundle] URLForResource:@"win"
withExtension:@"wav"];
AudioServicesCreateSystemSoundID((__bridge CFURLRef)soundURL,
&_winSoundID);
}
AudioServicesPlaySystemSound(_winSoundID);
self.winLabel.text = @"WINNER!";
[self performSelector:@selector(showButton)
withObject:nil
afterDelay:1.5];
}

The first method is used to show the button. As noted previously, we’re going to hide the button when the user taps it because, if the wheels are already spinning, there’s no point in letting them spin again until they’ve stopped.

The second method will be called when the user wins. First, we check if we have already loaded the winning sound. Properties are initialized as zero and valid identifiers for loaded sounds are not zero, so we can check whether the sound is loaded yet by comparing the identifier to zero. To load a sound, we first ask the main bundle for the path to the sound called win.wav, just as we did when we loaded the property list for the Dependent picker view. Once we have the path to that resource, the next three lines of code load the sound file in and play it. Next, we set the label toWINNER! and call the showButton method; however, we call the showButton method in a special way using a method called performSelector:withObject:afterDelay:. This is a very handy method available to all objects. It lets you call the method sometime in the future—in this case, one and a half seconds in the future, which will give the dials time to spin to their final locations before telling the user the result.

Note You may have noticed something a bit odd about the way we called the AudioServicesCreateSystemSoundID function. That function takes a URL as its first parameter, but it doesn’t want an instance of NSURL. Instead, it wants a CFURLRef structure. Apple provides C interfaces to many common components—such as URLs, arrays, strings, and much more—via the Core Foundation framework. This allows even applications written entirely in C some access to the functionality that we normally use from Objective-C. The interesting thing is that these C components are “bridged” to their Objective-C counterparts, so that a CFURLRef is functionally equivalent to an NSURL pointer, for example. That means that certain kinds of objects created in Objective-C can be pushed over the bridge to use C APIs, and vice versa. This is accomplished by using a C language cast, putting the type you want your variable to be interpreted as inside parentheses before the variable name. Starting in iOS 5, with the use of ARC, the type name itself must be preceded by the keyword __bridge, which gives ARC a hint about how it should handle this Objective-C object as it passes into a C API call.

We also need to make some changes to the spin: method. We will write code to play a sound and to call the playWinSound method if the player won. Make the following changes to the spin: method now:

- (IBAction)spin:(id)sender {
BOOL win = NO;
int numInRow = 1;
int lastVal = -1;
for (int i = 0; i < 5; i++) {
int newValue = random() % [self.images count];

if (newValue == lastVal) {
numInRow++;
} else {
numInRow = 1;
}
lastVal = newValue;

[self.picker selectRow:newValue inComponent:i animated:YES];
[self.picker reloadComponent:i];
if (numInRow >=3) {
win = YES;
}
}
if (_crunchSoundID == 0) {
NSString *path = [[NSBundle mainBundle] pathForResource:@"crunch"
ofType:@"wav"];
NSURL *soundURL = [NSURL fileURLWithPath:path];
AudioServicesCreateSystemSoundID((__bridge CFURLRef)soundURL,
&_crunchSoundID);
}
AudioServicesPlaySystemSound(_crunchSoundID);

if (win) {
[self performSelector:@selector(playWinSound)
withObject:nil
afterDelay:.5];
} else {
[self performSelector:@selector(showButton)
withObject:nil
afterDelay:.5];
}
self.button.hidden = YES;
self.winLabel.text = @" "; // Note the space between the quotes

if (win) {
self.winLabel.text = @”WINNER!”;
} else {
self.winLabel.text = @””;
}
}

First, we load the crunch sound if needed, just as we did with the win sound before. Now play the crunch sound to let the player know the wheels have been spun. Next, instead of setting the label to WINNER! as soon as we know the user has won, we do something tricky. We call one of the two methods we just created, but we do it after a delay using performSelector:afterDelay:. If the user won, we call our playWinSound method half a second into the future, which will give time for the dials to spin into place; otherwise, we just wait a half a second and reenable the Spin button. While waiting for the result, we hide the button and clear the label’s text.

Now you’re done! Hit the Xcode Run button and click the final tab to see and hear this slot machine in action. Tapping the Spin button should play a little cranking sound, and a win should produce a winning sound. Hooray!

Final Spin

By now, you should be comfortable with tab bar applications and pickers. In this chapter, we built a full-fledged tab bar application containing five different content views from scratch. You learned how to use pickers in a number of different configurations, how to create pickers with multiple components, and even how to make the values in one component dependent on the value selected in another component. You also saw how to make the picker display images rather than just text.

Along the way, you learned about picker delegates and data sources, and saw how to load images, play sounds, and create dictionaries from property lists. It was a long chapter, so congratulations on making it through! When you’re ready to tackle table views, turn the page and we’ll keep going.