In the clocks application, the timer screen shows a picker (probably a UIPicker
in UIDatePickerModeCountDownTimer
mode) with some text in the selec
Embossing is explained here: Adding Emboss to a UILabel in a navigationItem.titleView (as seen with navigationItem.title)
I've turned dizy's answer into a category on UIPickerView
a couple years ago. Just verified that it still works with iOS SDK 4.3, and posting it here. It allows you to add a label (XX hours) and animate changes to this label (e.g. 1 hour -> 3 hours) just like UIDatePicker
does.
// UIPickerView_SelectionBarLabelSupport.h
//
// This file adds a new API to UIPickerView that allows to easily recreate
// the look and feel of UIDatePicker labeled components.
//
// Copyright (c) 2009, Andrey Tarantsov <andreyvit@gmail.com>
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#import <Foundation/Foundation.h>
// useful constants for your font size-related code
#define kPickerViewDefaultTitleFontSize 20.0f
#define kDatePickerTitleFontSize 25.0f
#define kDatePickerLabelFontSize 21.0f
@interface UIPickerView (SelectionBarLabelSupport)
// The primary API to add a label to the given component.
// If you want to match the look of UIDatePicker, use 21pt as pointSize and 25pt as the font size of your content views (titlePointSize).
// (Note that UIPickerView defaults to 20pt items, so you need to use custom views. See a helper method below.)
// Repeated calls will change the label with an animation effect similar to UIDatePicker's one.
//
// To call this method on viewDidLoad, please call [pickerView layoutSubviews] first so that all subviews
// get created.
- (void)addLabel:(NSString *)label ofSize:(CGFloat)pointSize toComponent:(NSInteger)component leftAlignedAt:(CGFloat)offset baselineAlignedWithFontOfSize:(CGFloat)titlePointSize;
// A helper method for your delegate's "pickerView:viewForRow:forComponent:reusingView:".
// Creates a propertly positioned right-aligned label of the given size, and also handles reuse.
// The actual UILabel is a child of the returned view, use [returnedView viewWithTag:1] to retrieve the label.
- (UIView *)viewForShadedLabelWithText:(NSString *)label ofSize:(CGFloat)pointSize forComponent:(NSInteger)component rightAlignedAt:(CGFloat)offset reusingView:(UIView *)view;
// Creates a shaded label of the given size, looking similar to the labels used by UIPickerView/UIDatePicker.
- (UILabel *)shadedLabelWithText:(NSString *)label ofSize:(CGFloat)pointSize;
@end
And the implementation:
// UIPickerView_SelectionBarLabelSupport.m
//
// This file adds a new API to UIPickerView that allows to easily recreate
// the look and feel of UIDatePicker labeled components.
//
// Copyright (c) 2009, Andrey Tarantsov <andreyvit@gmail.com>
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#import "UIPickerView_SelectionBarLabelSupport.h"
// used to find existing component labels among UIPicker's children
#define kMagicTag 89464534
// a private UIKit implementation detail, but we do degrade gracefully in case it stops working
#define kSelectionBarClassName @"_UIPickerViewSelectionBar"
// used to sort per-component selection bars in a left-to-right order
static NSInteger compareViews(UIView *a, UIView *b, void *context) {
CGFloat ax = a.frame.origin.x, bx = b.frame.origin.x;
if (ax < bx)
return -1;
else if (ax > bx)
return 1;
else
return 0;
}
@implementation UIPickerView (SelectionBarLabelSupport)
- (UILabel *)shadedLabelWithText:(NSString *)label ofSize:(CGFloat)pointSize {
UIFont *font = [UIFont boldSystemFontOfSize:pointSize];
CGSize size = [label sizeWithFont:font];
UILabel *labelView = [[[UILabel alloc] initWithFrame:CGRectMake(0, 0, size.width, size.height)] autorelease];
labelView.font = font;
labelView.adjustsFontSizeToFitWidth = NO;
labelView.shadowOffset = CGSizeMake(1, 1);
labelView.textColor = [UIColor blackColor];
labelView.shadowColor = [UIColor whiteColor];
labelView.opaque = NO;
labelView.backgroundColor = [UIColor clearColor];
labelView.text = label;
labelView.userInteractionEnabled = NO;
return labelView;
}
- (UIView *)viewForShadedLabelWithText:(NSString *)title ofSize:(CGFloat)pointSize forComponent:(NSInteger)component rightAlignedAt:(CGFloat)offset reusingView:(UIView *)view {
UILabel *label;
UIView *wrapper;
if (view != nil) {
wrapper = view;
label = (UILabel *)[wrapper viewWithTag:1];
} else {
CGFloat width = [self.delegate pickerView:self widthForComponent:component];
label = [self shadedLabelWithText:title ofSize:pointSize];
CGSize size = label.frame.size;
label.frame = CGRectMake(0, 0, offset, size.height);
label.tag = 1;
label.textAlignment = UITextAlignmentRight;
label.autoresizingMask = UIViewAutoresizingFlexibleHeight;
wrapper = [[[UIView alloc] initWithFrame:CGRectMake(0, 0, width, size.height)] autorelease];
wrapper.autoresizesSubviews = NO;
wrapper.userInteractionEnabled = NO;
[wrapper addSubview:label];
}
label.text = title;
return wrapper;
}
- (void)addLabel:(NSString *)label ofSize:(CGFloat)pointSize toComponent:(NSInteger)component leftAlignedAt:(CGFloat)offset baselineAlignedWithFontOfSize:(CGFloat)titlePointSize {
NSParameterAssert(component < [self numberOfComponents]);
NSInteger tag = kMagicTag + component;
UILabel *oldLabel = (UILabel *) [self viewWithTag:tag];
if (oldLabel != nil && [oldLabel.text isEqualToString:label])
return;
NSInteger n = [self numberOfComponents];
CGFloat total = 0.0;
for (int c = 0; c < component; c++)
offset += [self.delegate pickerView:self widthForComponent:c];
for (int c = 0; c < n; c++)
total += [self.delegate pickerView:self widthForComponent:c];
offset += (self.bounds.size.width - total) / 2;
offset += 2 * component; // internal UIPicker metrics, measured on a screenshot
offset += 4; // add a gap
CGFloat baselineHeight = [@"X" sizeWithFont:[UIFont boldSystemFontOfSize:titlePointSize]].height;
CGFloat labelHeight = [@"X" sizeWithFont:[UIFont boldSystemFontOfSize:pointSize]].height;
UILabel *labelView = [self shadedLabelWithText:label ofSize:pointSize];
labelView.frame = CGRectMake(offset,
(self.bounds.size.height - baselineHeight) / 2 + (baselineHeight - labelHeight) - 1,
labelView.frame.size.width,
labelView.frame.size.height);
labelView.tag = tag;
UIView *selectionBarView = nil;
NSMutableArray *selectionBars = [NSMutableArray array];
for (UIView *subview in self.subviews) {
if ([[[subview class] description] isEqualToString:kSelectionBarClassName])
[selectionBars addObject:subview];
}
if ([selectionBars count] == n) {
[selectionBars sortUsingFunction:compareViews context:NULL];
selectionBarView = [selectionBars objectAtIndex:component];
}
if (oldLabel != nil) {
[UIView beginAnimations:nil context:oldLabel];
[UIView setAnimationDuration:0.25];
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:@selector(YS_barLabelHideAnimationDidStop:finished:context:)];
oldLabel.alpha = 0.0f;
[UIView commitAnimations];
}
// if the selection bar hack stops working, degrade to using 60% alpha
CGFloat normalAlpha = (selectionBarView == nil ? 0.6f : 1.0f);
if (selectionBarView != nil)
[self insertSubview:labelView aboveSubview:selectionBarView];
else
[self addSubview:labelView];
if (oldLabel != nil) {
labelView.alpha = 0.0f;
[UIView beginAnimations:nil context:oldLabel];
[UIView setAnimationDuration:0.25];
[UIView setAnimationDelay:0.25];
labelView.alpha = normalAlpha;
[UIView commitAnimations];
} else {
labelView.alpha = normalAlpha;
}
}
- (void)YS_barLabelHideAnimationDidStop:(NSString *)animationID finished:(NSNumber *)finished context:(UIView *)oldLabel {
[oldLabel removeFromSuperview];
}
@end
Usage example (in a view controller):
- (void)updateFloorLabel {
NSInteger floor = [self.pickerView numberOfRowsInComponent:0] - [self.pickerView selectedRowInComponent:0];
NSString *suffix = @"th";
if (((floor % 100) / 10) != 1) {
switch (floor % 10) {
case 1: suffix = @"st"; break;
case 2: suffix = @"nd"; break;
case 3: suffix = @"rd"; break;
}
}
[self.pickerView addLabel:[NSString stringWithFormat:@"%@ Floor", suffix]
ofSize:21
toComponent:0
leftAlignedAt:50
baselineAlignedWithFontOfSize:25];
}
- (void)viewDidLoad {
...
[self.pickerView layoutSubviews];
[self updateFloorLabel];
...
}
- (UIView *)pickerView:(UIPickerView *)pickerView viewForRow:(NSInteger)row forComponent:(NSInteger)component reusingView:(UIView *)view {
NSString *s = [NSString stringWithFormat:@"%d", [pickerView numberOfRowsInComponent:0] - row];
return [pickerView viewForShadedLabelWithText:s ofSize:25 forComponent:0 rightAlignedAt:46 reusingView:view];
}
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component {
[self updateFloorLabel];
}
Enjoy!
I also faced same problem. You can see working example in my custom made time picker published on GitHub:
https://github.com/kgadzinowski/iOSSecondsTimerPicker
It does exactly what you want.
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
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
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