views:

4907

answers:

7

In the clocks application, the timer screen shows a picker (probably a UIPicker in UIDatePickerModeCountDownTimer mode) with some text in the selection bar ("hours" and "mins" in this case).

(edit) Note that these labels are fixed: They don't move when the picker wheel is rolling.

Is there a way to show such fixed labels in the selection bar of a standard UIPickerView component?

I did not find any API that would help with that. A suggestion was to add a UILabel as a subview of the picker, but that didn't work.


Answer

I followed Ed Marty's advice (answer below), and it works! Not perfect but it should fool people. For reference, here's my implementation, feel free to make it better...

- (void)viewDidLoad {
    // Add pickerView
    self.pickerView = [[UIPickerView alloc] initWithFrame:CGRectZero];
    [pickerView release];
    CGSize pickerSize = [pickerView sizeThatFits:CGSizeZero];
    CGRect screenRect = [[UIScreen mainScreen] applicationFrame];
    #define toolbarHeight     40.0
    CGFloat pickerTop = screenRect.size.height - toolbarHeight - pickerSize.height;
    CGRect pickerRect = CGRectMake(0.0, pickerTop, pickerSize.width, pickerSize.height);
    pickerView.frame = pickerRect;

    // Add label on top of pickerView
    CGFloat top = pickerTop + 2;
    CGFloat height = pickerSize.height - 2;
    [self addPickerLabel:@"x" rightX:123.0 top:top height:height];
    [self addPickerLabel:@"y" rightX:183.0 top:top height:height];
    //...
}

- (void)addPickerLabel:(NSString *)labelString rightX:(CGFloat)rightX top:(CGFloat)top height:(CGFloat)height {
#define PICKER_LABEL_FONT_SIZE 18
#define PICKER_LABEL_ALPHA 0.7
    UIFont *font = [UIFont boldSystemFontOfSize:PICKER_LABEL_FONT_SIZE];
    CGFloat x = rightX - [labelString sizeWithFont:font].width;

    // White label 1 pixel below, to simulate embossing.
    UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(x, top + 1, rightX, height)];
    label.text = labelString;
    label.font = font;
    label.textColor = [UIColor whiteColor];
    label.backgroundColor = [UIColor clearColor];
    label.opaque = NO;
    label.alpha = PICKER_LABEL_ALPHA;
    [self.view addSubview:label];
    [label release];

    // Actual label.
    label = [[UILabel alloc] initWithFrame:CGRectMake(x, top, rightX, height)];
    label.text = labelString;
    label.font = font;
    label.backgroundColor = [UIColor clearColor];
    label.opaque = NO;
    label.alpha = PICKER_LABEL_ALPHA;
    [self.view addSubview:label];
    [label release];
}
+3  A: 

There are 2 things you can do:

If each row and component in row is a simple text, than you can simply use the default UIPickerView implementation as is, and in your controller implement the following UIPickerViewDelegate methods :

  • - (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component to keep track of which row is selected

  • and return a different text for the selected row in your implementation of - (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component

If you need to have something other than text as the differentiator for the selected row, than you basically need to create your own CustomPickerView that derives from the UIPickerView and then

  • First implement the - (void)selectRow:(NSInteger)row inComponent:(NSInteger)component animated:(BOOL)animated and keep track of which row is selected.

  • Then implement the - (UIView *)viewForRow:(NSInteger)row forComponent:(NSInteger)component to generate a different view for the selected row.

A sample for using the UIPickerView or implementing custom UIPickerView is available in the SDK, called UICatalog.

keremk
Nice trick except for one thing: Using your method, labels are not fixed like "hours" in the Timer picker, as I requested.It still helps a bit I suppose, at least there's a hint as to what each picker component refers to. But as soon as you actually make it roll, the illusion disappears...
squelart
Also the UICatalog example does not have these fixed labels in the standard or custom UIPickerView's... This may mean that it is probably not provided by the framework. Which seems silly since they have implemented it for the Timer picker!
squelart
+1  A: 

Rather than adding a label within the UIPickerView, just stick it on top of it, as a sibling that overlaps it. The only thing that's problematic is how to get the same font. I don't know how to get that embossed look, but maybe somebody else does, in which case, it's not really a problem at all.

Ed Marty
A: 

Can you show where you define pickerTop and pickerSize?

    CGFloat pickerTop = timePicker.bounds.origin.y;
CGSize pickerSize = timePicker.bounds.size;

That is what I have, but pickerTop seems to be wrong.

mike

mikechambers
Code couldn't fit in comment, so I've put it in the answer above. Note the bit of magic (40), this may have to change depending on whether you're using a navigation bar or other things that take screen space above your app... If someone knows a better way, please tell me!
squelart
+4  A: 

Create your picker, create a label with a shadow, and push it to a picker's subview below the selectionIndicator view.

It would look something like this


UILabel *label = [[[UILabel alloc] initWithFrame:CGRectMake(135, 93, 80, 30)] autorelease];
label.text = @"Label";
label.font = [UIFont boldSystemFontOfSize:20];
label.backgroundColor = [UIColor clearColor];
label.shadowColor = [UIColor whiteColor];
label.shadowOffset = CGSizeMake (0,1);
[picker insertSubview:label aboveSubview:[picker.subviews objectAtIndex:5]]; 
//When you have multiple components (sections)...
//you will need to find which subview you need to actually get under
//so experiment with that 'objectAtIndex:5'
//
//you can do something like the following to find the view to get on top of
// define @class UIPickerTable;
// NSMutableArray *tables = [[NSMutableArray alloc] init];
// for (id i in picker.subviews) if([i isKindOfClass:[UIPickerTable class]]) [tables addObject:i];
// etc...

-- Pay it forward

dizy
Does this work for multiple components? I have 3 components, I am trying to add a label to each component. What I have observed is, all the 3 labels are added as subview to the first component. How do I find the view to insert the label subview so that the labels are visible appropriately?
Raj
A: 

To re-create the embossed look for the labels...just create a image with the Text, so that you can easily apply a very similar effect to the text...and then use UIImageViews instead of Labels

devguy