Tweaking the Picker UI
Once you’ve got a working picker view, you can start using some of the optional UIPickerViewDelegate
methods to dramatically alter its appearance. We’ll change the picker to display icons of the animals
rather than the animal names.
Adding the Image Resources and Data
I’ve supplied seven animal
image PNG files inside the Animals folder of the MatchPicker project
folder. Start by finding and dragging the image files into the Resources
folder of your project in Xcode. When prompted, choose to copy the
items if needed, as shown in Figure 8.
The UIPickerView can display any UIView or subclass of UIView
within its components. To display an image, we’ll need to add it to a
view and make it available to the picker. Despite this sounding a bit
daunting, we can create a new UIImageView instance and populate it with an image from our project resources using a single line:
[[UIImageView alloc] initWithImage:[UIImage imageNamed:<image name>]]
So, what do we do with these UIImageViews
once we create them? The same thing we did with the animal names and
animal sounds—we put them in an array. That way, we can simply pass the
appropriate image view to the picker in the exact same way we were
passing strings!
By the Way
Remember, if you want to address
the ultra-high resolution display of the iPhone 4, you can add image
resources with twice the vertical and horizontal resolution and a suffix
of @2x to your projects. They’ll automatically be displayed without any additional coding!
Start by updating the MatchPickerViewController.h file to declare an NSArray called animalImages. The final (for real!) interface file is displayed in Listing 7.
Listing 7.
#import <UIKit/UIKit.h>
@interface MatchPickerViewController : UIViewController <UIPickerViewDataSource, UIPickerViewDelegate> { NSArray *animalNames; NSArray *animalSounds; NSArray *animalImages; IBOutlet UILabel *lastAction; IBOutlet UILabel *matchResult; } @property (nonatomic, retain) UILabel *lastAction; @property (nonatomic, retain) UILabel *matchResult;
@end
|
Edit the viewDidLoad
method within MatchPickerViewController.m to include the code to
populate an array with seven image views corresponding to our animal PNG
files. This code should be added following the allocation and
initialization of the animalSounds or animalNames arrays:
animalImages=[[NSArray alloc]initWithObjects:
[[UIImageView alloc] initWithImage:[UIImage imageNamed:@"mouse.png"]],
[[UIImageView alloc] initWithImage:[UIImage imageNamed:@"goose.png"]],
[[UIImageView alloc] initWithImage:[UIImage imageNamed:@"cat.png"]],
[[UIImageView alloc] initWithImage:[UIImage imageNamed:@"dog.png"]],
[[UIImageView alloc] initWithImage:[UIImage imageNamed:@"snake.png"]],
[[UIImageView alloc] initWithImage:[UIImage imageNamed:@"bear.png"]],
[[UIImageView alloc] initWithImage:[UIImage imageNamed:@"pig.png"]],
nil
];
Next, update the dealloc method to release this new array when the application is finished with it:
- (void)dealloc {
[animalNames release];
[animalSounds release];
[animalImages release];
[lastAction release];
[matchResult release];
[super dealloc];
}
Using Views (with Images!) in a Picker View
Wouldn’t it be great if we could just provide the image view to the picker in place of the animal name string in pickerView:titleForRow:forComponent
and have it work? Guess what. It won’t. Unfortunately, pickers operate
in only one of two ways—either by displaying strings using the
aforementioned method or displaying custom views using the method pickerView:viewForRow:forComponent:reusingView—but not a combination.
What this means for us is that if
we want to display image views in the picker, everything else we want
to show will have to be a subclass of UIView as well. The animal sounds are strings, so to display them, we need to create something that is a subclass of UIView that contains the necessary text. That “something” is a UILabel. By creating UILabels from the strings in the animalSounds array, we can successfully populate the picker with both images and text.
Begin by commenting out the pickerView:titleForRow:forComponent in MatchPickerViewController.m, by placing /* before the start, and */
after the end of the method. Alternatively, you can just delete the
entire method (because we won’t really need it again in this project).
Now, enter the following implementation of pickerView:viewForRow:forComponent:reusingView from Listing 8.
Listing 8.
1: - (UIView *)pickerView:(UIPickerView *)pickerView viewForRow:(NSInteger)row 2: forComponent:(NSInteger)component reusingView:(UIView *)view { 3: if (component==animalComponent) { 4: return [animalImages objectAtIndex:row]; 5: } else { 6: UILabel *soundLabel; 7: soundLabel=[[UILabel alloc] initWithFrame:CGRectMake(0,0,100,32)]; 8: [soundLabel autorelease]; 9: soundLabel.backgroundColor=[UIColor clearColor]; 10: soundLabel.text=[animalSounds objectAtIndex:row]; 11: return soundLabel; 12: } 13: }
|
In lines 3–4, we check to see whether the component requested is the animal component, and if it is, we use the row parameter to return the appropriate UIImageView stored in the animalImages array. This is nearly identical to how we dealt with the strings earlier.
If the component parameter isn’t referring to the animal component, then we need to return a UILabel with the appropriate referenced row from the animalSounds array. This is handled in lines 6–11.
In line 6, we declare a UILabel named soundLabel.
Line 7 allocates and initializes soundLabel with a frame using the initWithFrame
method. Remember from earlier hours that views define a rectangular
area for the content that is displayed on the iPhone screen. To create
the label, we need to define the rectangle of its frame. The CGRectMake
function takes starting x,y values and ending x,y values to define the
height and width of a rectangle. In this example, we’ve defined a
rectangle that spans 0 to 100 points horizontally and 0 to 32 points
vertically.
Line 8 calls autorelease on the soundLabel object. This is a bit different from what we’ve done elsewhere. Why can’t we just release soundLabel at the end of the method like everything else? The answer is that we have to return soundLabel so the picker can use it—so we can’t just get rid of it. By using autorelease, we can hand off the object to the picker and relieve ourselves of the responsibility of releasing it.
Line 9 sets the background color attribute of the label to be transparent. As you learned with web views, [UIColor clearColor]
returns a color object configured as transparent. If we leave this line
out, the rectangle will not blend in with the background of the picker
view.
Line 10 sets the text of the label to the string in of the specified row in animalSounds.
Finally, line 11 returns the UILabel—ready for display.
You can use Build and
Run to run the application, but there’s still going to be a slight
issue: The rows aren’t quite the right size to accommodate the images.
Changing Row Sizes
To control the width and height of the rows in the picker components, two additional delegate methods can be implemented:
pickerView:rowHeightForComponent: Given a component number, this method should return the height, in points, of the row being displayed.
pickerView:widthForComponent: Given a component number, this method returns the width of that component, in points.
For this sample application,
some trial and error led me to determine that the animal component
should be 75 points wide, while the sound component looks best at around
150 points.
Both components should use a constant row height of 55 points.
Translating this into code, implement pickerView:rowHeightForComponent as follows:
- (CGFloat)pickerView:(UIPickerView *)pickerView
rowHeightForComponent:(NSInteger)component {
return 55.0;
}
Similarly, pickerView:widthForComponent becomes this:
- (CGFloat)pickerView:(UIPickerView *)pickerView
widthForComponent:(NSInteger)component {
if (component==animalComponent) {
return 75.0;
} else {
return 150.0;
}
}
With those small additions to MatchPickerViewController.m, the UIPickerView
project is complete! You should now have a good understanding of how to
create and customize pickers and how to manage a user’s interaction
with them.