Customize text color of UIDatePicker for iOS7 (jus

2019-01-21 05:38发布

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

5条回答
Summer. ? 凉城
2楼-- · 2019-01-21 06:09

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.

查看更多
在下西门庆
3楼-- · 2019-01-21 06:19
self.birthDatePicker.setValue(UIColor.whiteColor(), forKeyPath: "textColor")

This worked for me.

查看更多
小情绪 Triste *
4楼-- · 2019-01-21 06:23

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
查看更多
倾城 Initia
5楼-- · 2019-01-21 06:23

I found the trick that set font color to any color and also dont mess up with Today label.

datePicker.setValue(UIColor.whiteColor(), forKeyPath: "textColor")
datePicker.datePickerMode = .CountDownTimer
datePicker.datePickerMode = .DateAndTime //or whatever your original mode was

Here is the result:

And all the credits for this solution goes to this post in other thread: https://stackoverflow.com/a/33459744/1236334

查看更多
我只想做你的唯一
6楼-- · 2019-01-21 06:31

The surefire way to do this is to iterate through subviews, and subviews of subviews, until you find the class you're looking for (in this case, some kind of UILabel) and manually set properties on it.

To find the one you're looking for I suggest you use an app like X-ray or Reveal to inspect the view hierarchy to get the exact class name, and then:


for (UIView * subview in datePicker.subviews) {
    if ([subview isKindOfClass:NSClassFromString:@"_UISomePrivateLabelSubclass"]) {
        [subview setColor:...
        //do interesting stuff here
    }
}

This is a very fragile way of doing things, but it doesn't technically ever call a private API, and it wouldn't scare Apple. Back in iOS 5 I used to do things like this to make UIAlertViews bigger on iPads to have more text without the embedded scroll view.

A lot of trial and error might be necessary, and it may not look pretty, but it will work.

(As a very smart guy I know said: "all the code will be lost to the compiler, like tears in rain.")

查看更多
登录 后发表回答