Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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

like image 417
rnystrom Avatar asked Nov 24 '13 21:11

rnystrom


2 Answers

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 
like image 138
Sig Avatar answered Sep 16 '22 15:09

Sig


Here's a good working snippet. Tested on iOS 7.1, 8.0 and 8.1.

#if __IPHONE_OS_VERSION_MAX_ALLOWED > __IPHONE_8_1     #error "Check if this hack works on this OS" #endif      [self.datePicker setValue:[UIColor whiteColor] 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]; 

I've added a simple condition to break the compilation process if you're building for iOS > 8.1, because I can't be sure that this hack will work, and you don't want to have any crashes in production because of this.

The setHighlightsToday: selector is used because UIDatePicker is using [UIColor blackColor] by default to display the current date.

like image 36
arturgrigor Avatar answered Sep 19 '22 15:09

arturgrigor