can I change the font color of the datePicker in iOS7?

51,655

Solution 1

- (UIView *)pickerView:(UIPickerView *)pickerView viewForRow:(NSInteger)row forComponent:(NSInteger)component reusingView:(UIView *)view{

UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, pickerView.frame.size.width, 44)];
label.backgroundColor = [UIColor grayColor];
label.textColor = [UIColor whiteColor];
label.font = [UIFont fontWithName:@"HelveticaNeue-Bold" size:18];
label.text = [NSString stringWithFormat:@" %d", row+1];
return label;    
}

// number Of Components
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView{
return 1;
}

// number Of Rows In Component
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:   (NSInteger)component{
return 6;
}

Solution 2

I need similar for my app and have ended up going the long way round. It's a real shame there isn't an easier way to simply switch to a white text version of UIDatePicker.

The code below uses a category on UILabel to force the label's text colour to be white when the setTextColor: message is sent to the label. In order to not do this for every label in the app I've filtered it to only apply if it's a subview of a UIDatePicker class. Finally, some of the labels have their colours set before they are added to their superviews. To catch these the code overrides the willMoveToSuperview: method.

It would likely be better to split the below into more than one category but I've added it all here for ease of posting.

#import "UILabel+WhiteUIDatePickerLabels.h"
#import <objc/runtime.h>

@implementation UILabel (WhiteUIDatePickerLabels)

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [self swizzleInstanceSelector:@selector(setTextColor:)
                      withNewSelector:@selector(swizzledSetTextColor:)];
        [self swizzleInstanceSelector:@selector(willMoveToSuperview:)
                      withNewSelector:@selector(swizzledWillMoveToSuperview:)];
    });
}

// Forces the text colour of the label to be white only for UIDatePicker and its components
-(void) swizzledSetTextColor:(UIColor *)textColor {
    if([self view:self hasSuperviewOfClass:[UIDatePicker class]] ||
       [self view:self hasSuperviewOfClass:NSClassFromString(@"UIDatePickerWeekMonthDayView")] ||
       [self view:self hasSuperviewOfClass:NSClassFromString(@"UIDatePickerContentView")]){
        [self swizzledSetTextColor:[UIColor whiteColor]];
    } else {
        //Carry on with the default
        [self swizzledSetTextColor:textColor];
    }
}

// Some of the UILabels haven't been added to a superview yet so listen for when they do.
- (void) swizzledWillMoveToSuperview:(UIView *)newSuperview {
    [self swizzledSetTextColor:self.textColor];
    [self swizzledWillMoveToSuperview:newSuperview];
}

// -- helpers --
- (BOOL) view:(UIView *) view hasSuperviewOfClass:(Class) class {
    if(view.superview){
        if ([view.superview isKindOfClass:class]){
            return true;
        }
        return [self view:view.superview hasSuperviewOfClass:class];
    }
    return false;
}

+ (void) swizzleInstanceSelector:(SEL)originalSelector
                 withNewSelector:(SEL)newSelector
{
    Method originalMethod = class_getInstanceMethod(self, originalSelector);
    Method newMethod = class_getInstanceMethod(self, newSelector);

    BOOL methodAdded = class_addMethod([self class],
                                       originalSelector,
                                       method_getImplementation(newMethod),
                                       method_getTypeEncoding(newMethod));

    if (methodAdded) {
        class_replaceMethod([self class],
                            newSelector,
                            method_getImplementation(originalMethod),
                            method_getTypeEncoding(originalMethod));
    } else {
        method_exchangeImplementations(originalMethod, newMethod);
    }
}

@end

Solution 3

This is the simplest way and it works! I Promise. Create the UIDatePicker in the view, or connect it via a storyboard, then put the following code into the ViewDidLoad method. Replace "datepicker" in the first line with whatever you named your date picker.

Objective-C:

//Set Color of Date Picker
self.datePicker.datePickerMode = UIDatePickerModeDate;
[self.datePicker setValue:[UIColor colorWithRed:70/255.0f green:161/255.0f blue:174/255.0f alpha:1.0f] forKeyPath:@"textColor"];
SEL selector = NSSelectorFromString(@"setHighlightsToday:");
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[UIDatePicker instanceMethodSignatureForSelector:selector]];
BOOL no = NO;
[invocation setSelector:selector];
[invocation setArgument:&no atIndex:2];
[invocation invokeWithTarget:self.datePicker];

Solution 4

I went ahead and implemented a UIDatePicker replacement using UIControl and UIPicker for iOS 7. That enables you to edit everything you want, including text fonts and colors. The only thing I couldn't change is the color of the lines around the selected row.

There are things that could be further simplified and it displays only date, not time. This could serve as a starting point for anyone wanting to do something similar.

enter image description here

@interface SBDatePicker : UIControl

@property (nonatomic, strong, readwrite) NSDate *minimumDate;
@property (nonatomic, strong, readwrite) NSDate *maximumDate;
@property (nonatomic, strong, readwrite) NSDate *date;

- (void)setDate:(NSDate *)date animated:(BOOL)animated;

@end


#import "SBDatePicker.h"

const NSUInteger NUM_COMPONENTS = 3;

typedef enum {
    kSBDatePickerInvalid = 0,
    kSBDatePickerYear,
    kSBDatePickerMonth,
    kSBDatePickerDay
} SBDatePickerComponent;

@interface SBDatePicker () <UIPickerViewDataSource, UIPickerViewDelegate> {
    SBDatePickerComponent _components[NUM_COMPONENTS];
}

@property (nonatomic, strong, readwrite) NSCalendar *calendar;
@property (nonatomic, weak, readwrite) UIPickerView *picker;

@property (nonatomic, strong, readwrite) NSDateFormatter *dateFormatter;

@property (nonatomic, strong, readwrite) NSDateComponents *currentDateComponents;

@property (nonatomic, strong, readwrite) UIFont *font;

@end

@implementation SBDatePicker

#pragma mark - Life cycle

- (id)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];

    if (!self) {
        return nil;
    }

    [self commonInit];

    return self;
}

- (id)initWithCoder:(NSCoder *)coder {
    self = [super initWithCoder:coder];

    if (!self) {
        return nil;
    }

    [self commonInit];

    return self;
}

- (void)commonInit {
    self.tintColor = [UIColor whiteColor];
    self.font = [UIFont systemFontOfSize:23.0f];

    self.calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
    [self setLocale:[NSLocale currentLocale]];

    UIPickerView *picker = [[UIPickerView alloc] initWithFrame:self.bounds];
    picker.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
    picker.dataSource = self;
    picker.delegate = self;
    picker.tintColor = [UIColor whiteColor];

    self.date = [NSDate date];

    [self addSubview:picker];
    self.picker = picker;
}

- (CGSize)intrinsicContentSize {
    return CGSizeMake(320.0f, 216.0f);
}

#pragma mark - Setup

- (void)setMinimumDate:(NSDate *)minimumDate {
    _minimumDate = minimumDate;

    [self updateComponents];
}

- (void)setMaximumDate:(NSDate *)maximumDate {
    _maximumDate = maximumDate;

    [self updateComponents];
}

- (void)setDate:(NSDate *)date {
    [self setDate:date animated:NO];
}

- (void)setDate:(NSDate *)date animated:(BOOL)animated {
    self.currentDateComponents = [self.calendar components:(NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit)
                                                  fromDate:date];

    [self.picker reloadAllComponents];
    [self setIndicesAnimated:YES];
}

- (NSDate *)date {
    return [self.calendar dateFromComponents:self.currentDateComponents];
}

- (void)setLocale:(NSLocale *)locale {
    self.calendar.locale = locale;

    [self updateComponents];
}

- (SBDatePickerComponent)componentFromLetter:(NSString *)letter {
    if ([letter isEqualToString:@"y"]) {
        return kSBDatePickerYear;
    }
    else if ([letter isEqualToString:@"m"]) {
        return kSBDatePickerMonth;
    }
    else if ([letter isEqualToString:@"d"]) {
        return kSBDatePickerDay;
    }
    else {
        return kSBDatePickerInvalid;
    }
}

- (SBDatePickerComponent)thirdComponentFromFirstComponent:(SBDatePickerComponent)component1
                                       andSecondComponent:(SBDatePickerComponent)component2 {

    NSMutableIndexSet *set = [NSMutableIndexSet indexSetWithIndexesInRange:NSMakeRange(kSBDatePickerInvalid + 1, NUM_COMPONENTS)];
    [set removeIndex:component1];
    [set removeIndex:component2];

    return (SBDatePickerComponent) [set firstIndex];
}

- (void)updateComponents {
    NSString *componentsOrdering = [NSDateFormatter dateFormatFromTemplate:@"yMMMMd" options:0 locale:self.calendar.locale];
    componentsOrdering = [componentsOrdering lowercaseString];

    NSString *firstLetter = [componentsOrdering substringToIndex:1];
    NSString *lastLetter = [componentsOrdering substringFromIndex:(componentsOrdering.length - 1)];

    _components[0] = [self componentFromLetter:firstLetter];
    _components[2] = [self componentFromLetter:lastLetter];
    _components[1] = [self thirdComponentFromFirstComponent:_components[0] andSecondComponent:_components[2]];

    self.dateFormatter = [[NSDateFormatter alloc] init];
    self.dateFormatter.calendar = self.calendar;
    self.dateFormatter.locale = self.calendar.locale;

    [self.picker reloadAllComponents];

    [self setIndicesAnimated:NO];
}

- (void)setIndexForComponentIndex:(NSUInteger)componentIndex animated:(BOOL)animated {
    SBDatePickerComponent component = [self componentForIndex:componentIndex];
    NSRange unitRange = [self rangeForComponent:component];

    NSInteger value;

    if (component == kSBDatePickerYear) {
        value = self.currentDateComponents.year;
    }
    else if (component == kSBDatePickerMonth) {
        value = self.currentDateComponents.month;
    }
    else if (component == kSBDatePickerDay) {
        value = self.currentDateComponents.day;
    }
    else {
        assert(NO);
    }

    NSInteger index = (value - unitRange.location);
    NSInteger middleIndex = (INT16_MAX / 2) - (INT16_MAX / 2) % unitRange.length + index;

    [self.picker selectRow:middleIndex inComponent:componentIndex animated:animated];
}

- (void)setIndicesAnimated:(BOOL)animated {
    for (NSUInteger componentIndex = 0; componentIndex < NUM_COMPONENTS; componentIndex++) {
        [self setIndexForComponentIndex:componentIndex animated:animated];
    }
}

- (SBDatePickerComponent)componentForIndex:(NSInteger)componentIndex {
    return _components[componentIndex];
}

- (NSCalendarUnit)unitForComponent:(SBDatePickerComponent)component {
    if (component == kSBDatePickerYear) {
        return NSYearCalendarUnit;
    }
    else if (component == kSBDatePickerMonth) {
        return NSMonthCalendarUnit;
    }
    else if (component == kSBDatePickerDay) {
        return NSDayCalendarUnit;
    }
    else {
        assert(NO);
    }
}

- (NSRange)rangeForComponent:(SBDatePickerComponent)component {
    NSCalendarUnit unit = [self unitForComponent:component];

    return [self.calendar maximumRangeOfUnit:unit];
}

#pragma mark - Data source

- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView {
    return 3;
}

- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)componentIndex {
    return INT16_MAX;
}

#pragma mark - Delegate

- (CGFloat)pickerView:(UIPickerView *)pickerView widthForComponent:(NSInteger)componentIndex {
    SBDatePickerComponent component = [self componentForIndex:componentIndex];

    if (component == kSBDatePickerYear) {
        CGSize size = [@"0000" sizeWithAttributes:@{NSFontAttributeName : self.font}];

        return size.width + 25.0f;
    }
    else if (component == kSBDatePickerMonth) {
        CGFloat maxWidth = 0.0f;

        for (NSString *monthName in self.dateFormatter.monthSymbols) {
            CGFloat monthWidth = [monthName sizeWithAttributes:@{NSFontAttributeName : self.font}].width;

            maxWidth = MAX(monthWidth, maxWidth);
        }

        return maxWidth + 25.0f;
    }
    else if (component == kSBDatePickerDay) {
        CGSize size = [@"00" sizeWithAttributes:@{NSFontAttributeName : self.font}];

        return size.width + 25.0f;
    }
    else {
        return 0.01f;
    }
}

- (NSString *)titleForRow:(NSInteger)row forComponent:(SBDatePickerComponent)component {
    NSRange unitRange = [self rangeForComponent:component];
    NSInteger value = unitRange.location + (row % unitRange.length);

    if (component == kSBDatePickerYear) {
        return [NSString stringWithFormat:@"%li", (long) value];
    }
    else if (component == kSBDatePickerMonth) {
        return [self.dateFormatter.monthSymbols objectAtIndex:(value - 1)];
    }
    else if (component == kSBDatePickerDay) {
        return [NSString stringWithFormat:@"%li", (long) value];
    }
    else {
        return @"";
    }
}

- (NSInteger)valueForRow:(NSInteger)row andComponent:(SBDatePickerComponent)component {
    NSRange unitRange = [self rangeForComponent:component];

    return (row % unitRange.length) + unitRange.location;
}

- (BOOL)isEnabledRow:(NSInteger)row forComponent:(NSInteger)componentIndex {
    NSDateComponents *dateComponents = [[NSDateComponents alloc] init];
    dateComponents.year = self.currentDateComponents.year;
    dateComponents.month = self.currentDateComponents.month;
    dateComponents.day = self.currentDateComponents.day;

    SBDatePickerComponent component = [self componentForIndex:componentIndex];
    NSInteger value = [self valueForRow:row andComponent:component];

    if (component == kSBDatePickerYear) {
        dateComponents.year = value;
    }
    else if (component == kSBDatePickerMonth) {
        dateComponents.month = value;
    }
    else if (component == kSBDatePickerDay) {
        dateComponents.day = value;
    }

    NSDate *rowDate = [self.calendar dateFromComponents:dateComponents];

    if (self.minimumDate != nil && [self.minimumDate compare:rowDate] == NSOrderedDescending) {
        return NO;
    }
    else if (self.maximumDate != nil && [rowDate compare:self.maximumDate] == NSOrderedDescending) {
        return NO;
    }

    return YES;
}

- (UIView *)pickerView:(UIPickerView *)pickerView viewForRow:(NSInteger)row forComponent:(NSInteger)componentIndex reusingView:(UIView *)view {
    UILabel *label;

    if ([view isKindOfClass:[UILabel class]]) {
        label = (UILabel *) view;
    }
    else {
        label = [[UILabel alloc] init];
        label.font = self.font;
    }

    SBDatePickerComponent component = [self componentForIndex:componentIndex];
    NSString *title = [self titleForRow:row forComponent:component];

    UIColor *color;

    BOOL enabled = [self isEnabledRow:row forComponent:componentIndex];

    if (enabled) {
        color = [UIColor whiteColor];
    }
    else {
        color = [UIColor colorWithWhite:0.0f alpha:0.5f];
    }

    NSAttributedString *attributedTitle = [[NSAttributedString alloc] initWithString:title
attributes:@{NSForegroundColorAttributeName: color}];

    label.attributedText = attributedTitle;

    if (component == kSBDatePickerMonth) {
        label.textAlignment = NSTextAlignmentLeft;
    }
    else {
        label.textAlignment = NSTextAlignmentRight;
    }

    return label;
}

- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)componentIndex {
    SBDatePickerComponent component = [self componentForIndex:componentIndex];
    NSInteger value = [self valueForRow:row andComponent:component];

    if (component == kSBDatePickerYear) {
        self.currentDateComponents.year = value;
    }
    else if (component == kSBDatePickerMonth) {
        self.currentDateComponents.month = value;
    }
    else if (component == kSBDatePickerDay) {
        self.currentDateComponents.day = value;
    }
    else {
        assert(NO);
    }

    [self setIndexForComponentIndex:componentIndex animated:NO];

    NSDate *datePicked = self.date;

    if (self.minimumDate != nil && [datePicked compare:self.minimumDate] == NSOrderedAscending) {
        [self setDate:self.minimumDate animated:YES];
    }
    else if (self.maximumDate != nil && [datePicked compare:self.maximumDate] == NSOrderedDescending) {
        [self setDate:self.maximumDate animated:YES];
    }
    else {
        [self.picker reloadAllComponents];
    }

    [self sendActionsForControlEvents:UIControlEventValueChanged];
}

@end

Solution 5

I'm struggling with this too, black text on a black background. :) I haven't found a way to change the text color yet. You can however play with the background color:

picker.backgroundColor = [UIColor whiteColor];

Another hack I found that worked is giving every UIView in the datepicker (every number has it's own UIView, (yeah I know)

[UIView appearanceWhenContainedIn:[UITableView class], [UIDatePicker class], nil].backgroundColor = [UIColor colorWithWhite:1 alpha:0.5]

(I don't recommend using this option since it causes an annoying flickering effect while rotating.)

Hope this helps. :)

Share:
51,655
Jared Gross
Author by

Jared Gross

Updated on November 03, 2020

Comments

  • Jared Gross
    Jared Gross over 3 years

    just downloaded my copy of xcode 5 and was wondering if anyone knows how I can change the color or size of the font in the date picker?

  • rgettman
    rgettman over 10 years
    Please add some explanation to go along with the code in your answer.
  • Sulthan
    Sulthan over 10 years
    -1 Because the code contains no explanation. Are we supposed to subclass UIDatePicker or something?
  • Fahim Parkar
    Fahim Parkar over 10 years
    this code is not getting called when I use for my datepicker... This only works for UIPicker and not with UIDatePicker
  • bmueller
    bmueller over 10 years
    Any progress on this yet?
  • James Laurenstin
    James Laurenstin over 10 years
    Do you have the #import "NSMutableAttributedString+Append.h" code as well?
  • Sulthan
    Sulthan over 10 years
    Oh, thats just a simple category that I use to create atrributed strings. It's not required here. I will remove it from the source.
  • James Laurenstin
    James Laurenstin over 10 years
    Without it, it doesn't seem to work! The call to [attributedTitle appendString:title withColor:color]; Some help would be appreciated.
  • James Laurenstin
    James Laurenstin over 10 years
    Ok, forget it...I've figured it out with some minimal changes. Thanks It works.
  • Jon
    Jon over 10 years
    This solution is better than "rolling your own" solution, because it keeps locale settings.
  • SEG
    SEG over 10 years
    Ooh. Manual reference counting. Fancy ;) Thanks a lot!
  • Luke Wyatt
    Luke Wyatt over 10 years
    This worked for UIPicker well, explanation would be nice though.
  • c roald
    c roald over 10 years
    +1 just for using swizzling. I guess technically this isn't using any private APIs, even.
  • Gema Megantara
    Gema Megantara over 10 years
    How about the date validation, like February only have 28 days?
  • Sulthan
    Sulthan over 10 years
    @GemaMegantara Where is it missing?
  • LostInTheTrees
    LostInTheTrees over 10 years
    Really nice. Worked great and quick to add. No way I ever would have had the time to devote to figuring this one out myself. Thanks! It should get the check mark.
  • Stas Zhukovskiy
    Stas Zhukovskiy about 10 years
    will this solution pass App Store moderation? has anyone tried?
  • Ashish Mathur
    Ashish Mathur almost 10 years
    @Sulthan [attributedTitle appendString:title withColor:color]; I didn't get it
  • Sulthan
    Sulthan almost 10 years
    @AshishMathur That was from my internal category. I have just removed it because it was not needed in this piece of code.
  • Ashish Mathur
    Ashish Mathur almost 10 years
    I used NSAttributedString *attString = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@"%@",title] attributes:@{NSForegroundColorAttributeName:[UIColor whiteColor]}]; but it is showing only months component
  • Seamus
    Seamus almost 10 years
    Thanks for doing the work to figure this out. the catch with willMoveToSuperview is really nice. I also added swizzle methods for changing the font and case of the labels as well.
  • Clifton Labrum
    Clifton Labrum almost 10 years
    The amount of code required to do this is both impressive and terrifying. :)
  • Mayank Jain
    Mayank Jain over 9 years
    this is for PickerView..not for DatePicker
  • Matthias Bauch
    Matthias Bauch over 9 years
    Did you intent to swizzle the selector willMoveToSuperview:: (with two colons)?
  • Matthias Bauch
    Matthias Bauch over 9 years
    Additionally I think in swizzledWillMoveToSuperview: you should call setTextColor: so it calls your custom method? Currently by calling swizzledSetTextColor: you call the stock implementation. --- I try to figure out what I should do to get the "hours" and "min" labels white when I use a countDown UIDatePicker...
  • Matthias Bauch
    Matthias Bauch over 9 years
    I finally figured out the trick to turn the black "hours" and "min" label (countdown mode) white. These labels are just "there", setTextColor: is never called on these. So when I create the datePicker I traverse recursively through all the subviews of UIDatePicker and call view.textColor = view.textColor; on all views that where subclasses of UILabels.
  • Matthias Bauch
    Matthias Bauch over 9 years
    As it turns out once the "hours" label changes text it's black again. So you have to call that recursive textColor setting thingie in the value changed action again.
  • joslinm
    joslinm over 9 years
    All I wanted was a white color and this worked perfectly for me. Thanks!
  • jayellos
    jayellos about 9 years
    will this solution pass App Store moderation? has anyone tried?
  • David
    David about 9 years
    Code crashes when japanese locale is set. - (void)updateComponents can't parse this string: y年M月d日.
  • Brian Donovan
    Brian Donovan about 9 years
    I burned a lot of time on this, and this is the only one that has worked for UIDatePicker instances.
  • Gaurav
    Gaurav almost 9 years
    How can change font in UIDatePicker ?
  • Juan Boero
    Juan Boero almost 9 years
    @TriannaBrannon looks like NSInvocation is gone in swift : https://developer.apple.com/swift/blog/?id=19 therefore is not compiling.
  • lborgav
    lborgav over 8 years
    Where did the DesignHelper come from?
  • Jeremiah
    Jeremiah over 8 years
    Oops, ignore that. It's a custom class that returns a UIColor
  • lborgav
    lborgav over 8 years
    I tried it with UIColor.redColor() but it's not working
  • Maciej Trybiło
    Maciej Trybiło about 8 years
    In Swift datePicker.setValue(UIColor.whiteColor(), forKey: "textColor") datePicker.setValue(false, forKey: "highlightsToday")
  • Mehul Thakkar
    Mehul Thakkar about 8 years
    it is not working directly.. it is working once we scroll the picker
  • Bryan Bryce
    Bryan Bryce almost 8 years
    Top one works, bottom one doesn't Xcode 7.3.1, iOS 9.3
  • Nike Kov
    Nike Kov almost 8 years
    Bottom one throws exception.
  • Nick
    Nick almost 4 years
    this solution is extremely inefficient due to the superview traversal - (BOOL) view:(UIView *) view hasSuperviewOfClass:(Class) class. That code runs multiple times for EVERY UILabel regardless of what it is contained in.