在iOS7中自定义UIDatePicker的文本颜色(就像Mailbox一样)

36
我遇到了非常令人沮丧的困境。我进行了很多研究,明显可以看出Apple不希望我们篡改iOS7。但是,我想要篡改。而且,Mailbox团队清楚地知道如何做到这一点并获得批准。
我想要实现的主要目标是将标签颜色更改为白色。
我的第一个想法是他们正在使用一个自定义UIPickerView来模仿UIDatePicker,但我认为情况并非如此。
我放大了一小段内容,发现了普通的UIDatePicker的残留部分(黑线),以及字母"W"的剪裁。
现在我已经搜索了高低。进行了一些运行时的黑客攻击,尝试了UIAppearance,并甚至深入了一些私有API,只是想看看是否可能。
我离成功很近,非常接近,但它使用了一个私有API,如果您滚动得足够快,则标签会再次变为黑色。
我完全不知道如何在不违反规则或花费无数小时重新实现UIDatePicker的情况下完成这项工作。
Mailbox,请告诉我您的秘密!如果其他人有任何建议(我指的是任何),请告诉我。
另外,这是我最接近的一次尝试:

你有什么问题?你想从标准的UIDatePicker中获得什么不同的功能?这些图片来自于你的应用程序还是其他应用程序? - rmaddy
我会进行编辑以澄清这个问题。感谢你的提醒! - rnystrom
5个回答

59

我需要为我的应用程序提供类似的功能,但最终只能采取冗长的方式实现。真遗憾没有更简单的方法来切换到UIDatePicker的白色文本版本。

下面的代码使用了UILabel的分类,强制使标签的文本颜色在发送setTextColor:消息时变为白色。为了不对应用程序中的每个标签都这样做,我已将其过滤,仅适用于UIDatePicker类的子视图。最后,某些标签在添加到其父视图之前就已经设置了它们的颜色。为了捕获这些标签,代码覆盖了willMoveToSuperview:方法。

很可能最好将以下内容分成多个类别,但我已将所有内容都放在这里以方便发布。

#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

3
天才!感谢!苹果是否有可能批准这个? - ChrisBorg
1
@Gal 这个得到批准了吗? - DevC
1
我会自己尝试,我的应用程序现在处于审核阶段。 - Atef
1
这种技术有没有被批准用于任何应用程序? - mattblessed
2
该应用程序自2014年12月起已被苹果公司批准 :) - Atef
显示剩余16条评论

30

这是一个良好的工作代码片段。在iOS 7.18.08.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];

如果您正在构建 iOS > 8.1,我已添加了一个简单的条件来中断编译过程,因为我无法确定这个黑客是否会起作用,并且您不希望由于此原因在生产中遇到任何崩溃。

setHighlightsToday:选择器被使用,因为UIDatePicker默认使用[UIColor blackColor]来显示当前日期。


只需几行代码,这就可以被接受为答案,非常棒,非常感谢。 - smoothumut
非常好用!但在iOS 8.1中存在问题,即当年文本颜色无法更改。请就此向我提供建议。 - Bhavesh Dhaduk
1
嗨,如果我使用这段代码,"Today"标签会被更改为日期(例如:Wed Apr 10)。你们有没有遇到同样的问题?除此之外,一切都很好。 - Klemen
在 Swift 中:picker.sendAction("setHighlightsToday:", to: nil, forEvent: nil) - davidrelgr

15

我发现了一个诀窍,可以将字体颜色设置为任何颜色,并且不会与“今天”标签混淆。

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

这是结果:

此解决方案所有功劳均归于另一个帖子: https://dev59.com/Pl4c5IYBdhLWcg3wAWTv#33459744


这是一个比使用setHighlightsToday更好的解决方案,有两个原因...它不会用日期替换"Today",也不会在Swift 2.2中产生编译器警告。 - Jim Rhoades

10
self.birthDatePicker.setValue(UIColor.whiteColor(), forKeyPath: "textColor")

这对我起作用了。


你知道其他标签键路径是什么吗? - keegan3d
1
在 Swift 中,您可以取消今天的文本颜色: picker.sendAction(“setHighlightsToday:”,to:nil,forEvent:nil) - davidrelgr
1
有趣的是,这个方法不适用于属性的didSet,但在viewDidLoad下可以使用。 - diegotrevisan

3

一个确保可以实现这个的方法是遍历子视图,和子视图的子视图,直到找到你正在寻找的类(在这种情况下,某种UILabel),并手动设置其属性。

为了找到你需要的那个组件,我建议使用像X-ray或Reveal这样的应用程序来检查视图层次结构以获取确切的类名,然后:


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

这是一种非常脆弱的做法,但从技术上讲,它并没有调用私有API,也不会让苹果感到惊恐。在iOS 5时期,我曾经使用这样的方法来使UIAlertViews在iPad上更大,以便嵌入更多文本而不需要滚动视图。
可能需要进行很多试错,看起来可能不太美观,但它能够起作用。
(正如我认识的一个非常聪明的人所说:“所有的代码都将被编译器遗忘,就像雨中的眼泪。”)

我尝试过这个,但不幸的是私有类<code>_UIDatePickerView</code>(https://github.com/nst/iOS-Runtime-Headers/blob/master/Frameworks/UIKit.framework/_UIDatePickerView.h)在重用视图方面使用了类似于<code>UITableView</code>的东西。因此,在创建视图并开始滚动后,你会得到到处都是<code>UILabel</code>的混乱。看看这个链接,以查看所有的混乱:http://blog.ittybittyapps.com/blog/2013/09/20/lifting-the-lid-on-ios-7s-uipicker/ - rnystrom

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接