UIPickerView (resizing)
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 usefultransform
property, which performs arbitrary affine transformations on the view. This lets you do a lot of neat tricks, including scaling theUIPickerView
.Example
Here are the header and implementation files for a
PickerDemo
class, which is a view controller that manages a view containing aUIPickerView
. (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 theUIPickerView
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];
}
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.
No comments:
Post a Comment