One of the prettiest built-in views on the iPhone is the UIPickerView
, which presents a slot-machine-like list of options. Unfortunately, this view isn’t particularly easy to use, as its default presentation takes up quite a bit of screen real-estate, and it’s not obvious how to resize it. Today, I’d like to talk about how to tame this view.
Things That Just Ain’t So
When I was Googling for tips on how to control the size of UIPickerViews
, I repeatedly came across the following pieces of advice:
- Pass in a small
CGRect
toinitWithFrame
- Make the
UIPickerView
a subview of anotherUIView
, and then resize the outer view
Neither technique worked for me. (OS 2.2.1)
Affine Transformations
Semi-buried in the UIView
class is the astonishingly useful transform
property, which performs arbitrary affine transformations on the view. This lets you do a lot of neat tricks, including scaling the UIPickerView
.
Example
Here are the header and implementation files for a PickerDemo
class, which is a view controller that manages a view containing a UIPickerView
. (I nest the UIPickerView
inside another UIView
s.t. the PickerDemo
controller can be pushed onto a Navigation Controller, with which I was playing when I pulled together this demo.)
#import
@interface PickerDemo : UIViewController <UIPickerViewDelegate, UIPickerViewDataSource> {
UIPickerView* picker;
}
@end
#import "PickerDemo.h"
@implementation PickerDemo
- (void)loadView
{
picker = [[UIPickerView alloc] initWithFrame:CGRectZero];
picker.delegate = self;
picker.dataSource = self;
picker.showsSelectionIndicator = YES;
self.view = [[UIView alloc] initWithFrame:CGRectZero];
[self.view addSubview:picker];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning]; // Releases the view if it doesn't have a superview
// Release anything that's not essential, such as cached data
}
- (void)dealloc {
[picker release];
[super dealloc];
}
#pragma mark UIPickerViewDelegate methods
- (NSString*)pickerView:(UIPickerView*)pv titleForRow:(NSInteger)row forComponent:(NSInteger)component
{
return [NSString stringWithFormat:@"%d",row];
}
#pragma mark UIPickerViewDataSource methods
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView*)pv
{
return 3;
}
- (NSInteger)pickerView:(UIPickerView*)pv numberOfRowsInComponent:(NSInteger)component
{
return 10;
}
@end
This yields a simple, 3-reel UIPickerView
, where each reel contains choices from 0 to 9. No (meaningful) CGRect
is given for either the UIPickerView
or its enclosing UIView
; the former always assumes its default size, while the latter receives its geometry from the (not shown here) Navigation Controller. The result may be seen to the right.
Scaling
Lets try adding a simple scaling CGAffineTransform
, by recoding the loadView
function as follows:
- (void)loadView
{
picker = [[UIPickerView alloc] initWithFrame:CGRectZero];
picker.delegate = self;
picker.dataSource = self;
picker.showsSelectionIndicator = YES;
picker.transform = CGAffineTransformMakeScale(0.5, 0.5);
self.view = [[UIView alloc] initWithFrame:CGRectZero];
[self.view addSubview:picker];
}
This nicely scales down the UIPickerView
. Unfortunately, the transform is applied to the center of the view, not the upper-left corner, which probably isn’t what one wants. (At least, it’s not what I want.)
Composites
To do a scale relative to the UL corner, we’ll need to execute three transforms:
- Move the UL corner to the origin
- Scale relative to the origin
- Move the origin to the UL corner
We can call these three transformations T0
, S0
, and T1
, and since Cocoa/Core Graphics uses row vectors, we will compose them into a single transformation C
with this formula:
C = T0*S0*T1
A re-written loadView
incorporating this transform would look like this:
- (void)loadView
{
picker = [[UIPickerView alloc] initWithFrame:CGRectZero];
picker.delegate = self;
picker.dataSource = self;
picker.showsSelectionIndicator = YES;
CGAffineTransform t0 = CGAffineTransformMakeTranslation(picker.bounds.size.width/2, picker.bounds.size.height/2);
CGAffineTransform s0 = CGAffineTransformMakeScale(0.5, 0.5);
CGAffineTransform t1 = CGAffineTransformMakeTranslation(-picker.bounds.size.width/2, -picker.bounds.size.height/2);
picker.transform = CGAffineTransformConcat(t0, CGAffineTransformConcat(s0, t1));
self.view = [[UIView alloc] initWithFrame:CGRectZero];
[self.view addSubview:picker];
}
This yields the desired result.
Digression
To be honest, I’m not really happy with the technique I used above to compute the translation offsets; the documentation states that:
The origin of the transform is the value of the center property, or the layer’s anchorPoint property if it was changed. (Use the layer property to get the underlying Core Animation layer object.)
The center
property is given in the co-ordinate system of the view’s superview; I’m not familiar with the details of the anchorPoint
property. In the code above, I sort of hand-wave over all of this, and just assume that the origin of the transform will end up in the middle of the bounds
rectangle, which is given in the view’s coordinate system. That seems to be true in this case, and might be true in the general case, but it is a hand-wave, and I thought it only fair to point it out.
Reading over that, I’m not sure it clarified anything, but let’s move on.
Brevity
You can compress the transformation composition code quite a bit:
- (void)loadView
{
picker = [[UIPickerView alloc] initWithFrame:CGRectZero];
picker.delegate = self;
picker.dataSource = self;
picker.showsSelectionIndicator = YES;
CGFloat dX=picker.bounds.size.width/2, dY=picker.bounds.size.height/2;
picker.transform = CGAffineTransformTranslate(CGAffineTransformScale(CGAffineTransformMakeTranslation(-dX, -dY), 0.5, 0.5), dX, dY);
self.view = [[UIView alloc] initWithFrame:CGRectZero];
[self.view addSubview:picker];
}
Note that the transformations are assembled “backwards” in this idiom; the “innermost” or “first” transformation appears on the “outside” of the expression (its arguments appear last, on the right). Essentially, these functions take an existing transform and prepend a newly defined one to its left.
You can download the final header and implementation files, if you’re interested in them.
Pingback: Tweets that mention Things that were not immediately obvious to me » Blog Archive » UIPickerView (resizing) -- Topsy.com