18

We have extended UILabel to be able to apply standard fonts and colors for all uses of a given label type in our apps. Eg.

@interface UILabelHeadingBold : UILabel
@end

In our AppDelegate, we apply fonts and colors like this

[[UILabelHeadingBold appearance] setTextColor:<some color>];
[[UILabelHeadingBold appearance] setFont:<some font>];

When adding a UILabel in our XIB's, we can now select the class to be of type UILabelHeadingBold, and it works as expected. The label is shown with the correct font and color, as specified in our AppDelegate.

However, if we create a label programmatically, eg.

UILabelHeadingBold *headingLabel = [[UILabelHeadingBold alloc] initWithFrame:CGRectMake(10, 10, 100, 30)];
[self.mainView addSubview:headingLabel];

the UILabel does not get the expected font/color applied. We have to manually apply these attributes.

Is there a way to make UIAppearance take effect on programatically created UI elements, or does it only work when used within XIB's?

Cœur
  • 37,241
  • 25
  • 195
  • 267
ckibsen
  • 943
  • 1
  • 7
  • 9

5 Answers5

20

From Apple documentation :

To support appearance customization, a class must conform to the UIAppearanceContainer protocol and relevant accessor methods must be marked with UI_APPEARANCE_SELECTOR.

For example in UINavigationBar.h, tintColor is marked with UI_APPEARANCE_SELECTOR

@property(nonatomic,retain) UIColor *tintColor UI_APPEARANCE_SELECTOR;

But in UILabel.h you can see that the textColor and font propertys are not marked with UI_APPEARANCE_SELECTOR but somehow it works when added in Interface Builder (following the documentation it shouldn't work at all).

Moxy
  • 4,162
  • 2
  • 30
  • 49
  • 2
    It *does* sometimes work for UILabels created in code too, but results are inconsistent, and behaviour is different between iOS 5 and, well, you know. So just don't use `UIAppearance` to customize `UILabel`s – Joshua J. McKinnon Aug 07 '12 at 12:01
  • +1 for the information, that I'm not the only one who gets inconsistent results (took me hours coding until I found your comment). +1 for robert.wijas solution - that I use now - and everything is fine. – anneblue Aug 22 '13 at 11:19
14

Simple hack that is working for me with no issues is to create a category with a UIAppearance setter that modifies UILabel properties.

Following UIAppearance conventions I created a method:

- (void)setTextAttributes:(NSDictionary *)numberTextAttributes;
{
    UIFont *font = [numberTextAttributes objectForKey:UITextAttributeFont];
    if (font) {
        self.font = font;
    }
    UIColor *textColor = [numberTextAttributes objectForKey:UITextAttributeTextColor];
    if (textColor) {
        self.textColor = textColor;
    }
    UIColor *textShadowColor = [numberTextAttributes objectForKey:UITextAttributeTextShadowColor];
    if (textShadowColor) {
        self.shadowColor = textShadowColor;
    }
    NSValue *shadowOffsetValue = [numberTextAttributes objectForKey:UITextAttributeTextShadowOffset];
    if (shadowOffsetValue) {
        UIOffset shadowOffset = [shadowOffsetValue UIOffsetValue];
        self.shadowOffset = CGSizeMake(shadowOffset.horizontal, shadowOffset.vertical);
    }
}

In UILabel category:

@interface UILabel (UISS)

- (void)setTextAttributes:(NSDictionary *)numberTextAttributes UI_APPEARANCE_SELECTOR;

@end

I'm still trying to figure out why the original setter does not work.

Robert Wijas
  • 693
  • 7
  • 14
  • We create custom properties in a subclass that work with the appearance proxy. E.g. a "titleLabelFont" property to wrap UIButton's titleLabel.font property. It works for other properties too, such as shadows. – Dan Reese Jan 15 '13 at 17:44
  • @robert +1 very nice solution. Took me hours coding until I found your answer. Thanks for that. Now, everything works fine (and simple). – anneblue Aug 22 '13 at 11:20
  • Great solution, this works well on other views too, such as UIButton for setting the titleLabel font as Apple deprecated the setFont: method. – Michael Gaylord Sep 19 '13 at 10:22
  • yes but I'm not able to change text color after for some other UILabels – Injectios Jan 27 '16 at 17:46
6

I was having this exact same issue, but in Swift. A custom UILabel's appearance would work if added from a storyboard, but not if added from code.

Here's a solution I found in Swift that's working for me:

class MyLabel: UILabel { }

extension UILabel {
    @objc dynamic var customFont: UIFont! {
        get { return self.font }
        set { self.font = newValue }
    }

    @objc dynamic var customColor: UIColor! {
        get { return self.textColor }
        set {  self.textColor = newValue }
    }
}

Then add these lines where you configure your app appearance:

MyLabel.appearance().customFont = UIFont.systemFont(ofSize: 20)
MyLabel.appearance().customColor = UIColor.magenta
kwahn
  • 2,118
  • 2
  • 21
  • 17
0

@robert.wijas solution works great !

For iOS 7 and upwards I had to update the key since the one he used are deprecated for 7+ :

- (void)setTextAttributes:(NSDictionary *)numberTextAttributes;
{
    UIFont *font = [numberTextAttributes objectForKey:NSFontAttributeName];
    if (font) {
        self.font = font;
    }
    UIColor *textColor = [numberTextAttributes objectForKey:NSForegroundColorAttributeName];
    if (textColor) {
        self.textColor = textColor;
    }
    UIColor *textShadowColor = [numberTextAttributes objectForKey:NSShadowAttributeName];
    if (textShadowColor) {
        self.shadowColor = textShadowColor;
    }
    NSValue *shadowOffsetValue = [numberTextAttributes objectForKey:NSShadowAttributeName];
    if (shadowOffsetValue) {
        UIOffset shadowOffset = [shadowOffsetValue UIOffsetValue];
        self.shadowOffset = CGSizeMake(shadowOffset.horizontal, shadowOffset.vertical);
    }
}
Matthieu Riegler
  • 31,918
  • 20
  • 95
  • 134
0

A workaround that I've used is to manually apply the color from the appearance that is set:

let label = UILabel()
label.textColor = UILabel.appearance().textColor

This way you don't need to reference anything new, or explicitly define the color. This also works for context specific coloring:

label.textColor = UILabel.appearance(whenContainedInInstancesOf:[MyView.self]).textColor
Jbryson
  • 2,875
  • 1
  • 31
  • 53