1

I am trying to specify the font family of every label in my iOS app in a way that makes it fairly easy to change them later. I don't want to have to go through Interface Builder and reset every font on every screen. According to this post, I have created a method that will find all the fonts in a view and set them accordingly.

In my case, there are a few different font families I need to use based on whether the font is bold, italic, or light (e.g. skinny). These are all located in separate files such as "OpenSans-Semibold.ttf", "OpenSans-Italic.ttf", and "OpenSans-Light.ttf".

Ideally, I would like to be able to set the font to bold, italic, or light in Interface Builder, then have the code override just the font family, using the appropriate .ttf file. According to this post, I can pretty easily detect whether the font has been set to bold or italic, but finding out whether it's light or not doesn't seem to be working.

For the light fonts, the value of "traits" is 0x0--so no flags are set. Is there another way to detect light fonts?

Code looks like this:

- (void) setFontFamily:(UIView*)view
{
    if([view isKindOfClass:[UILabel class]])
    {
        UILabel* label = (UILabel*)view;

        UIFontDescriptorSymbolicTraits traits = label.font.fontDescriptor.symbolicTraits;
        BOOL bold = traits & UIFontDescriptorTraitBold;
        BOOL italic = traits & UIFontDescriptorTraitItalic;

        if(bold)
            [label setFont:[UIFont fontWithName:@"OpenSans-Semibold"size:label.font.pointSize]];
        else if(italic)
            [label setFont:[UIFont fontWithName:@"OpenSans-Italic"size:label.font.pointSize]];
        else if(light)
            [label setFont:[UIFont fontWithName:@"OpenSans-Light"size:label.font.pointSize]];
        else
            [label setFont:[UIFont fontWithName:@"OpenSans"size:label.font.pointSize]];
    }

    for(UIView* subView in view.subviews)
        [self setFontFamily:subView];
}
Community
  • 1
  • 1
d512
  • 32,267
  • 28
  • 81
  • 107
  • 2
    Is it possible that you're misunderstanding what "light" is? italic and bold are _symbolic_ traits. "light" is a _weight_ trait. There is nothing in your code that has anything to do with weight. – matt Mar 27 '14 at 21:40
  • 2
    Oh, sorry, one more thing: on iOS 7, are you aware of UIFontDescriptor? You might find it more pleasant to use. – matt Mar 27 '14 at 21:47
  • It is quite possible that I am misunderstanding what the technical definition of light is. Isn't bold a weight trait though? Light is essentially the same thing in the opposite direction. Anyway, I don't see anything in the API related to font weight (there is no CTFontWeightTraits for example). Is there such a thing? – d512 Mar 27 '14 at 21:56
  • Good suggestion on using UIFontDescriptor, I changed the code to reflect that. – d512 Mar 27 '14 at 22:03
  • "there is no CTFontWeightTraits for example" What is `kCTFontWeightTrait` then? Have you taken time to look over the Constants section of the documentation? – matt Mar 28 '14 at 00:16
  • Yes, I've looked at the documentation. The only thing I see in the UIFontDescriptor docs related to weights is the constant UIFontWeightTrait and I'm not really sure how to use it. The documentation says you can use it "to retrieve information about a font descriptor from its trait dictionary", however Googling for code that uses that constant does not yield much. I'm not sure exactly what "traits dictionary" they are talking about. This doesn't seem to be a well documented or explored aspect of the SDK. If you have some ideas, I would love to hear them. – d512 Mar 30 '14 at 02:42
  • It seems to me you're missing the whole point of UIFontDescriptor. You just take the features of the first font, hand them to `fontDescriptorWithFontAttributes:`, and it gives you the new font. You don't do all that nonsense with explicit font names at all. – matt Mar 30 '14 at 04:01
  • That's not going to work because I have separate .ttf files for each version of the font I need to use. I can't just let the system apply bold or italics to the font as it sees fit. I'll update the original post to make that more clear. – d512 Mar 31 '14 at 17:29
  • I don't think you are grasping what "gives you the new font" means. It figures out that you have an italic version of the font and that is the font it hands you back when you ask for a certain font plus the italic attribute. It does not "apply" anything. - That is the whole point; it knows about the characteristics of all the available fonts and finds the one that meets the specifications you give. – matt Mar 31 '14 at 18:24
  • Basically the code you give is trying to do something hard-coded and rather clumsily that font descriptor is built-in to do elegantly already. – matt Mar 31 '14 at 18:26

1 Answers1

1

Your entire approach to determining a font based on its characteristics is problematic:

else if(italic)
    [label setFont:
        [UIFont fontWithName:@"OpenSans-Italic"size:label.font.pointSize]];

You are hard-coding the font name based on the trait. Instead, ask the runtime for the font based on the name and trait. In this very simple example I find out what installed font, if any, is an italic variant of Gill Sans:

UIFont* f = [UIFont fontWithName:@"GillSans" size:15];
CTFontRef font2 =
    CTFontCreateCopyWithSymbolicTraits (
        (__bridge CTFontRef)f, 0, nil, 
         kCTFontItalicTrait, kCTFontItalicTrait);
UIFont* f2 = CFBridgingRelease(font2);

Note that this code is valid in iOS 7 only, where CTFontRef and UIFont are toll-free bridged to one another. In theory it should be possible to do this without C functions through UIFontDescriptor, but the last time I looked it was buggy and didn't work for all fonts (e.g. Gill Sans!).

That is what you should be doing: determine the symbolic and weight traits of your starting font, and then ask the runtime for the font that most close matches your requirements.

matt
  • 515,959
  • 87
  • 875
  • 1,141