-
A UIPickerView with labels
Posted on March 29th, 2009 13 commentsI recently needed a picker view with labels (like the one in the timer tab in the Clock app) to select minutes and seconds for a time interval. So I made the following subclass of UIPickerView:
-
#import
-
-
/**
-
A picker view with labels under the selection indicator.
-
Similar to the one in the timer tab in the Clock app.
-
NB: has only been tested with less than four wheels.
-
*/
-
@interface LabeledPickerView : UIPickerView {
-
NSMutableDictionary *labels;
-
}
-
-
/** Adds the label for the given component. */
-
-
@end
… and the implementation:
-
/*******************************************************************************
-
* Copyright (c) 2009 Kåre Morstøl (NotTooBad Software).
-
* All rights reserved. This program and the accompanying materials
-
* are made available under the terms of the Eclipse Public License v1.0
-
* which accompanies this distribution, and is available at
-
* http://www.eclipse.org/legal/epl-v10.html
-
*
-
* Contributors:
-
* Kåre Morstøl (NotTooBad Software) – initial API and implementation
-
*******************************************************************************/
-
-
// http://stackoverflow.com/questions/367471/fixed-labels-in-the-selection-bar-of-a-uipickerview/616517
-
-
#import "LabeledPickerView.h"
-
-
@implementation LabeledPickerView
-
-
/** loading programmatically */
-
- (id)initWithFrame:(CGRect)aRect {
-
if (self = [super initWithFrame:aRect]) {
-
}
-
return self;
-
}
-
-
/** loading from nib */
-
if (self = [super initWithCoder:coder]) {
-
}
-
return self;
-
}
-
-
- (void) dealloc
-
{
-
[labels release];
-
[super dealloc];
-
}
-
-
#pragma mark Labels
-
-
}
-
-
/**
-
Adds the labels to the view, below the selection indicator glass-thingy.
-
The labels are aligned to the right side of the wheel.
-
The delegate is responsible for providing enough width for both the value and the label.
-
*/
-
- (void)didMoveToWindow {
-
// exit if view is removed from the window or there are no labels.
-
if (!self.window || [labels count] == 0)
-
return;
-
-
UIFont *labelfont = [UIFont boldSystemFontOfSize:20];
-
-
// find the width of all the wheels combined
-
CGFloat widthofwheels = 0;
-
for (int i=0; i
-
widthofwheels += [self rowSizeForComponent:i].width;
-
}
-
-
// find the left side of the first wheel.
-
// seems like a misnomer, but that will soon be corrected.
-
CGFloat rightsideofwheel = (self.frame.size.width - widthofwheels) / 2;
-
-
// cycle through all wheels
-
for (int component=0; component
-
// find the right side of the wheel
-
rightsideofwheel += [self rowSizeForComponent:component].width;
-
-
// get the text for the label.
-
// move on to the next if there is no label for this wheel.
-
if (text) {
-
-
// set up the frame for the label
-
CGRect frame;
-
frame.size = [text sizeWithFont:labelfont];
-
// center it vertically
-
frame.origin.y = (self.frame.size.height / 2) - (frame.size.height / 2) - 0.5;
-
// align it to the right side of the wheel, with a margin.
-
// use a smaller margin for the rightmost wheel.
-
frame.origin.x = rightsideofwheel - frame.size.width -
-
(component == self.numberOfComponents - 1 ? 5 : 7);
-
-
// set up the label
-
UILabel *label = [[[UILabel alloc] initWithFrame:frame] autorelease];
-
label.text = text;
-
label.font = labelfont;
-
label.backgroundColor = [UIColor clearColor];
-
label.shadowColor = [UIColor whiteColor];
-
label.shadowOffset = CGSizeMake(0,1);
-
-
/*
-
and now for the tricky bit: adding the label to the view.
-
kind of a hack to be honest, might stop working if Apple decides to
-
change the inner workings of the UIPickerView.
-
*/
-
if (self.showsSelectionIndicator) {
-
// if this is the last wheel, add label as the third view from the top
-
if (component==self.numberOfComponents-1)
-
[self insertSubview:label atIndex:[self.subviews count]-3];
-
// otherwise add label as the 5th, 10th, 15th etc view from the top
-
else
-
[self insertSubview:label aboveSubview:[self.subviews objectAtIndex:5*(component+1)]];
-
} else
-
// there is no selection indicator, so just add it to the top
-
[self addSubview:label];
-
}
-
}
-
}
-
-
@end
A big thanks to dizy from stackoverflow.com for showing how to add the labels below the selection indicator.
If anyone knows of a better place to put the label-adding code than didMoveToWindow then please let me know. It seems out of place where it is now.
This class is part of the mySettings project and the latest version can always be found here: LabeledPickerView.h, LabeledPickerView.m.
13 responses to “A UIPickerView with labels”

-
Very nice and very helpful. Thank you.
However I have a need to update the labels on the fly. So for example changingthe picker from 2 to 1 will change my label from “minutes” to “minute” etc.
I’m not 100% sure how I can do this. I’ve tried adding a tag to the label setup just below label.shadowoffset:
label.tag = component;
And then I’ve added an update routine to the class as follows:
- (void) upDateLabel:(NSString *)labeltext forComponent:(NSUInteger)component {
UILabel *theLabel = [self viewWithTag:component];
theLabel.text = labeltext;
}This returns a warning for starters (initialisation from distinct objective-c type) but when I run it, it does appear to update the value for the view (in the debugger). However the label in the pickerView never changes.
Any ideas or pointers on how to do this correctly?
Thanks, Michael.
-
Hi Kare,
Turns out it was a bug in another bit of code….so everything is working now (you don’t even need setNeedsDisplay).
I still have the warning though….which I’m trying to sort now. When I do I’ll come back to you.
Thanks, Michael.
-
Ok done…I needed to cast the viewWithTag to a UILabel of course,
I’m sure there is a better way to do this but for the moment using the component as the tag is the easiest way because nothing else in your code needs to be modified. Also it makes it very easy to find the label view regardless of where it is added.
So please go ahead and add this in.
Thanks again.
//add the following line
- (void)didMoveToWindow {
…
label.tag = component;
…
}// Update method
- (void) upDateLabel:(NSString *)labeltext forComponent:(NSUInteger)component {
UILabel *theLabel = [self viewWithTag:component];
theLabel.text = labeltext;
//[theLabel setNeedsDisplay];
}Obviously the best place to call the update method is in the pickerView didSelectRow method for the component being updated. i.e. [thePicker upDateLabel:NSLocalizedString(@"minute", @"") forComponent:1];
Thanks again for doing all the hard work.
Regards, Michael
-
In fact Apple animates the label change so you could do this to replicate what apple does (not sure about the timings though!:
- (void) upDateLabel:(NSString *)labeltext forComponent:(NSUInteger)component {
UILabel *theLabel = (UILabel*)[self viewWithTag:component];[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.75];
[UIView setAnimationCurve:UIViewAnimationCurveLinear];
theLabel.alpha = 0.00;
theLabel.text = labeltext;
theLabel.alpha = 1.00;
[UIView commitAnimations];
} -
sorry! In fact you only want to update the label if the label value has changed:
- (void) upDateLabel:(NSString *)labeltext forComponent:(NSUInteger)component {
UILabel *theLabel = (UILabel*)[self viewWithTag:component];// Update label if it doesn’t match current label
if (theLabel.text != labeltext) {
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.75];
[UIView setAnimationCurve:UIViewAnimationCurveLinear];
theLabel.alpha = 0.00;
theLabel.text = labeltext;
theLabel.alpha = 1.00;
[UIView commitAnimations];
}}
-
Michael July 17th, 2009 at 06:52
Thanks for this useful little class! It’s strange that the iPhone HIG recommends doing exactly this, yet doesn’t suggest how to do it, nor do the APIs seem to support it easily!
By the way, I noticed that this class always tries to send a pickerView:didSelectRow:inComponent: message to its delegate, which crashes the app if the delegate doesn’t implement that method. The regular UIPickerView doesn’t do this, and indeed that method is listed as “optional” in the UIPickerViewDelegate protocol reference.
Otherwise, very nice work. Thanks again!
-
This is great stuff!
I did notice, however, that its calling ‘didSelectRow’ upon creation (or at least when added to view). I haven’t narrowed it down exactly but has anyone else seen this?
-
dispenses employ a great web-site decent Gives thank you for the work to support people
-
JeffW July 19th, 2010 at 20:26
I’m working on a iOS 4.0 app, when I try to use a LabledPickerView I get an EXE_BAD_ACCESS at this line:
if ([self.delegate respondsToSelector:@selector(pickerView:didSelectRow:inComponent:)])
[self.delegate pickerView:self didSelectRow:[self selectedRowInComponent:component] inComponent:component];Has this code been broken by an Apple update, or am I doing something wrong?
Thanks!
Leave a reply
-


Michael Kaye March 30th, 2009 at 17:37