MOBILE

iPhone Application Development : Implementing a Custom Picker View (part 2)

2/27/2011 10:17:23 AM

Finishing the Interface

To complete the interface for the MatchPicker application, we need two labels (UILabel)—one for providing feedback of what the user just selected, another for showing whether the user successfully made a match. These labels will, in turn, connect to the lastAction and matchResult outlets, respectively.

Adding the Output Labels

Drag two labels into the view, positioning one above the other. Change the title of the top label to read Last Action and the bottom label to read Match Feedback. In our sample project, we’ve also chosen to set the attributes so that both labels are centered and the Match Feedback label is larger (24 pt) than the Last Action label, as shown in Figure 4.

Figure 4. Add two labels to the view to handle output to the user.

Connecting the Outlets

Connect each of the labels to their corresponding outlets by Control-dragging from the File’s Owner icon to each label and then choosing the lastAction and matchResult outlets, as appropriate. Figure 5 demonstrates the connection from the Match Feedback label to its matchResult outlet.

Figure 5. Make sure you remember to connect the two labels to their outlets!

With that simple step, we’re done with Interface Builder. All the work of actually customizing the appearance of the picker view must take place in Xcode.

Providing Data to the Picker

A big difference between the UIPickerView we’re using now and other controls, such as the UISegmentedControl, is that what the control displays is determined entirely by the code we write. There is no “point and click” to edit the different components and values within Interface Builder. WYSIWYG isn’t an option.

So, what information do we need to provide? Remember that the picker displays scrolling wheels called components. Within each component are any number of “rows” that display the values you want users to select. We’ll need to provide the picker with data for each row in each component. For this example, we’ll have one component with animal names and another with animal sounds.

Creating the Application Data Structures

Because the picker displays lists of information, it stands to reason that we’d want to store the data that they display as lists—a perfect job for an array! We’ll create two arrays, animalNames and animalSounds, that contain all the information that the picker will display to the user.

We want these arrays to be available to everything in the MatchPickerViewController class, so we first need to add them to the @interface block within the view controller header file. Edit MatchPickerViewController.h to include two NSArrays (animalNames and animalSounds) as shown in Listing 2.

Listing 2.
#import <UIKit/UIKit.h>

@interface MatchPickerViewController : UIViewController
<UIPickerViewDataSource, UIPickerViewDelegate> {
NSArray *animalNames;
NSArray *animalSounds;
IBOutlet UILabel *lastAction;
IBOutlet UILabel *matchResult;
}

@property (nonatomic, retain) UILabel *lastAction;
@property (nonatomic, retain) UILabel *matchResult;

@end

These two arrays correspond to the components that we’ll be displaying in the picker. Components are numbered starting at zero, left to right, so, assuming we want the names of the animals to be on the left and sounds on the right, component 0 will correspond to the animalNames array and component 1 to animalSounds.

Before going any further, take a few seconds to add the appropriate releases for these arrays within the dealloc method. The current version of the method should read as follows:

- (void)dealloc {
[animalNames release];
[animalSounds release];
[lastAction release];
[matchResult release];
[super dealloc];
}

Populating the Data Structures

After the arrays have been declared, they need to be filled with data. The easiest place to do this is in the viewDidLoad method of the view controller (MatchPickerViewController.m). Edit MatchPickerViewController.m by uncommenting the viewDidLoad method and adding the lines, as shown in Listing 3, to allocate and initialize the arrays with a list of animals and sounds.

Listing 3.
- (void)viewDidLoad {
animalNames=[[NSArray alloc]initWithObjects:
@"Mouse",@"Goose",@"Cat",@"Dog",@"Snake",@"Bear",@"Pig",nil];
animalSounds=[[NSArray alloc]initWithObjects:
@"Oink",@"Rawr",@"Ssss",@"Roof",@"Meow",@"Honk",@"Squeak",nil];
}

Watch Out!

nil is needed to denote the end of the array initialization list, so if it’s missing, your application will almost certainly crash!


The arrays are initialized with a series of strings, but if you look closely, the strings don’t match up! This is intentional. It wouldn’t make sense to display the strings so that the animal name immediately matches the animal sound. Instead, element 0 of animalNames matches element 6 of animalSounds, animalNames element 1 matches animalSounds element 5, 2 matches 4, and so on. When it gets time to handle checking for a match between the components, we’ll use this logic:

The total number of sounds, minus 1, minus the sound the user has chosen must be the same as the chosen animal.

So, for a total of 7 sounds, where element 5 is chosen, we get 7 – 1 – 5 = 1 (exactly the number we want). You’re welcome to implement your own display and matching logic. This is just a quick way of “mixing things up” a bit for the purposes of this project.

To help simplify the application a bit, it would be nice if we could symbolically refer to “component 0” as the “animalComponent” and “component 1” as the “soundComponent.” By defining a few constants at the start of our implementation file, we can do just that! Edit MatchPickerViewController.m and add these lines so that they precede the #import line:

#define componentCount 2
#define animalComponent 0
#define soundComponent 1

The first constant componentCount is just the number of components that we want to display in the picker, whereas the other two constants, animalComponent and soundComponent, can be used to refer to the different components in the picker without resorting to using their actual numbers.

What’s Wrong with Referring to Something by its Number?

Absolutely nothing. The reason that it is helpful to use constants, however, is that if your design changes and you decide to change the order of the components or add another component, you can just change the numbering within the constants rather than each place they’re used in the code. This will make a bit more sense in a few minutes as we start implementing the delegate and data source protocol methods.


Our application has everything it requires data-wise—it just needs the methods to get the data into the picker view.

Implementing the Picker Data Source Methods

Despite a promising sounding name, the picker data source methods (described by the UIPickerViewDataSource protocol) really only provide a small amount of information to the picker via these methods:

numberOfComponentsInPickerView: Returns the number of components the picker should display

pickerView:numberOfRowsInComponent: Returns the number of rows that the picker will be displaying within a given component

Let’s start with component count. Edit MatchPickerViewController.m and add the following method to the file:

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

We already defined a constant, componentCount, with the number of components we want to display (2), so the entire implementation of this method is just the line return componentCount.

The second method, pickerView:numberOfRowsInComponent, is expected to return the number of rows contained within a given component. We’ll make use of the NSArray method count to return the number of items within the array that is going to make up the component. For example, to get back the number of names in the animalNames array, we can use this:

[animalNames count]

Implement it using the code in Listing 4.

Listing 4.
- (NSInteger)pickerView:(UIPickerView *)pickerView
numberOfRowsInComponent:(NSInteger)component {
if (component==animalComponent) {
return [animalNames count];
} else {
return [animalSounds count];
}
}

Here, we just compare the component variable provided when the picker calls the method to the animalComponent constant we declared earlier. If they are equal, we return a count of the names in animalNames. Otherwise, we return a count of the number of sounds in animalSounds.

Congratulations, you’ve just completed the methods required for conforming to the UIPickerViewDataSource protocol!

Populating the Picker Display

Where the data source protocol methods are responsible for defining how many items will appear in the picker, the UIPickerViewDelegate methods define what items are shown, and how they are displayed. There’s only a single method we really need before we can start looking at the results of our work: pickerView:titleForRow:forComponent. This method is called by the picker view to determine what text should be shown in a given component and row.

For example, if component 0 and row 0 are provided to the method as parameters, the method should return Mouse, because it is the first element of our animalNames array, which corresponds to component 0 in the picker.

This method requires the ability to retrieve a string from one of our arrays. The NSArray instance method objectAtIndex is exactly what we need. To retrieve row 5 from the animalSounds array we could use this:

[animalSounds objectAtIndex:5]

The pickerView:titleForRow:forComponent method provides us with both a row variable and a component variable. The implementation is shown in Listing 5.

Listing 5.
- (NSString *)pickerView:(UIPickerView *)pickerView
titleForRow:(NSInteger)row forComponent:(NSInteger)component {
if (component==animalComponent) {
return [animalNames objectAtIndex:row];
} else {
return [animalSounds objectAtIndex:row];
}
}


The code first checks to see whether the supplied component variable is equal to the animalComponent constant that we configured, and then, if it is, returns the object (a string) from the specified row of the animalNames array. If component isn’t equal to animalComponent, we can assume that we need to be looking at the animalSounds array and return a string from it instead.

By the Way

Because we have just two components, we’re making the assumption that if a method isn’t referencing one, it must be referencing the other. Obviously if you have more than two components, you need a more complicated if-then-else structure or a switch statement.


After adding the method to MatchPickerViewController.m, save the file, and then choose Build and Run. The application will launch and show the picker view, complete with the contents of your two arrays, much like Figure 6.

Figure 6. The application should now run and show the customized picker view.


Notice that although the picker does work, choosing values has no effect—that’s because we need to implement one more delegate method before our efforts will truly pay off.

Other  
  •  Windows Phone 7 Development : Isolated Storage - Working with Isolated Storage Settings
  •  Mobile Application Security : WebOS Security - Permissions and User Controls
  •  Mobile Application Security : WebOS Security - Code Security
  •  Windows Phone 7 Development : Working with Isolated Directory Storage (part 2)
  •  Windows Phone 7 Development : Working with Isolated Directory Storage (part 1)
  •  iPhone Application Development : Making Multivalue Choices with Pickers - Using Date Pickers (part 3)
  •  iPhone Application Development : Making Multivalue Choices with Pickers - Using Date Pickers (part 2) - Adding a Date Picker
  •  iPhone Application Development : Making Multivalue Choices with Pickers - Using Date Pickers (part 1)
  •  iPhone Application Development : Making Multivalue Choices with Pickers - Understanding Pickers
  •  Sync Your iPad with iTunes : Troubleshooting iTunes and the Sync
  •  Sync Your iPad with iTunes : Manually Transferring Music, Movies, Podcasts, and More on Your iPad (Drag-and-Drop Method)
  •  Windows Phone 7 Development : Internationalization - Using Resource Files to Localize Content
  •  Windows Phone 7 Development : Internationalization - Storing and Retrieving Current Culture Settings
  •  Mobile Application Security : WebOS Security - Development and Security Testing
  •  Mobile Application Security : WebOS Security - Introduction to the Platform
  •  iPhone Application Development : Getting the User’s Attention - Using Alert Sounds and Vibrations
  •  iPhone Application Development : Getting the User’s Attention - Using Action Sheets
  •  jQuery 1.3 : Modifying table appearance (part 4) - Filtering
  •  jQuery 1.3 : Modifying table appearance (part 3) - Collapsing and expanding sections
  •  jQuery 1.3 : Modifying table appearance (part 2) - Tooltips
  •  
    Top 10
    Kyocera Mita FS-C5150DN
    MSI Power Edition GeForce GTX 670
    Transcend's SATA III SSD720 - Safer Data
    Asus HD7970 - A Card For Enthusiasts, By Enthusiasts.
    Printers : Can't Live With Them, Can't Live Without Them (Part 1)
    Printers: Can't Live With Them, Can't Live Without Them (Part 2)
    Setting Up Multiple Accounts With OS X Lion
    Data Storage Considerations (Part 1)
    Data Storage Considerations (Part 2)
    Sennheiser HD700 - Welcome To Audio Heaven
    Most View
    Transact-SQL in SQL Server 2008 : Row Constructors
    Collaborating via Web-Based Communication Tools : Evaluating Instant Messaging Services
    The AmigaOS and App News!
    What's Your Strategy For Today’s Server Room Challenges?
    Deploying a Public Key Infrastructure with Windows Server 2008 R2
    B.M.C. Audio upgrades Arcadia
    Huge Screen Supertest (Part 3)
    Samsung Galaxy Ace 2 Reviews (Part 2)
    Birds Of Prey (Part 2) - Owl in motion, Prey, Little owl, Expression, Goshawk, Perch
    Windows Server 2003 : Building an Active Directory Structure (part 1) - The First Domain
    Filtering Out Evil with Firewalls (part 2)
    Buying Guide: CPU Cooling Equipment (Part 4) - Thermaltake BigWater 760 Plus,Thermaltake Frio OCK,Zalman CNPS20LQ
    Windows Server 2008: Active Directory Infrastructure - Detailing Real-World Replication Designs
    Get A Faster, Safer PC (Part 3) - Make text easier to read, Disable a laptop touchpad
    Ivy Bridge Laptop Attacks The Market
    Windows Server 2008 R2 Active Directory Domain Services Primer : Outlining the Role of DNS in AD DS
    Download Web Content Asynchronously
    Mobile Phone Game Programming : Using Sprite Animation - Achieving Smooth Animation with the GameCanvas Class
    Choosing The Right Camera For You (Part 1) - Nikon D3100, Sony NEX C3
    H8-1090D Desktop PC - Elite Class