Customize text color of UIDatePicker for iOS7 (just like Mailbox does)

rnystrom picture rnystrom · Nov 24, 2013 · Viewed 21.1k times · Source

I'm having the most frustrating dilemma. I've researched up and down and can clearly see that Apple does not want us tampering with iOS 7. Well, I want to tamper. And, the team at Mailbox clearly figured out how to do it and get approved.

The main thing that I'm trying to achieve is to change the label color to white.

Mailbox UIDatePicker

My first thought was they are using a custom UIPickerView that just mimics a UIDatePicker, but I just don't think this is the case.

I zoomed in on a small fragment and discovered remnants of a normal UIDatePicker (black lines) along with clipping on the letter "W".

Mailbox UIDatePicker fragments

Now I've scoured high and low. Did some runtime hacking, messed with UIAppearance, and even dug into some private APIs just to see if this is possible.

I got close, very close, but it used a private API, and if you scrolled fast enough the labels would turn black again.

I'm completely at a loss on how to do this without a) breaking the rules or b) spending countless hours reimplementing UIDatePicker.

Mailbox, tell me your secrets! And if anyone else has any suggestions (and I mean any), please let me know.

Also, this is the closest I've gotten:

So close yet so far

Answer

Sig picture Sig · Jan 7, 2014

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 lable 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