8

I am rendering numbers in iOS (targeting 7 and up) by storing them in an NSAttributedString and rendering with "drawAtPoint:". I am using Helvetica Neue.

I have noticed that digits of numbers drawn like this are not proportional – the glyphs all have the same width. Even a skinny "1" takes up the same space as a "0".

A test confirms this:

for(NSInteger i=0; i<10; ++i)
{
  NSString *iString = [NSString stringWithFormat: @"%d", i];
  const CGSize iSize = [iString sizeWithAttributes: [self attributes]];
  NSLog(@"Size of %d is %f", i, iSize.width);
}

With, elsewhere:

-(NSDictionary *) attributes
{
  static NSDictionary * attributes;
  if(!attributes)
  {
    attributes = @{
                   NSFontAttributeName: [UIFont systemFontOfSize:11],
                   NSForegroundColorAttributeName: [UIColor whiteColor]
                   };
  }
  return attributes;
}

This resulting glyphs all have the same width of 6.358 points.

Is there some rendering option I can turn on that to enable proportional digit glyphs? Is there another font (ideally similar to Helvetica Neue) that supports proportional digit glyphs (ideally, built in)? Anything else?

Thank you.

Benjohn
  • 13,228
  • 9
  • 65
  • 127
  • There's a discussion about the use of proportional verses "tabular" (another term for monospace) "figures" and "numerals" at [link](http://www.fonts.com/content/learning/fontology/level-3/numbers/proportional-vs-tabular-figures). By searching with that terminology I found this [link](http://typographica.org/on-typography/beyond-helvetica-the-real-story-behind-fonts-in-ios-7/) on iOS 7 font rendering, which I think will let me self answer this. I'll update when done. – Benjohn Nov 14 '13 at 10:21
  • This pdf [link](http://docs.huihoo.com/apple/wwdc/2013/session_223__using_fonts_with_text_kit.pdf) of slides from an Apple presentations provides the code needed. Important to know is that to use these features you must to `#import `: that's where the symbols are defined. – Benjohn Nov 14 '13 at 10:59

1 Answers1

19

iOS 7 lets you specify fonts using UIFontDescriptor instances. A UIFont instance is then obtained from a descriptor.

Given a UIFontDescriptor it is also possible to obtain a customisation of it that changes some characteristics by using the method [fontDescriptor fontDescriptorByAddingAttributes: attibutes] where attributes is an NSDictionary of font attributes.

Apple documents the attributes in the UIFontDescriptor reference.

From the reference, one particular font descriptor attribute UIFontDescriptorFeatureSettingsAttribute lets you provide "An array of dictionaries representing non-default font feature settings. Each dictionary contains UIFontFeatureTypeIdentifierKey and UIFontFeatureSelectorIdentifierKey."

Documentation of the UIFontFeatureTypeIdentifierKeys and UIFontFeatureSelectorIdentifierKeys is in Apple's Font Registry documentation. The specific case of proportional digits is covered in this pdf of slides of an Apple presentation, so I just lifted that.

This code that will take an existing UIFont instance and give you back a new instance with proportional digits:

// You'll need this somewhere at the top of your file to pull
// in the required constants.
#import <CoreText/CoreText.h>

…

UIFont *const existingFont = [UIFont preferredFontForTextStyle: UIFontTextStyleBody];
UIFontDescriptor *const existingDescriptor = [existingFont fontDescriptor];

NSDictionary *const fontAttributes = @{
 // Here comes that array of dictionaries each containing UIFontFeatureTypeIdentifierKey 
 // and UIFontFeatureSelectorIdentifierKey that the reference mentions.
 UIFontDescriptorFeatureSettingsAttribute: @[
     @{
       UIFontFeatureTypeIdentifierKey: @(kNumberSpacingType),
       UIFontFeatureSelectorIdentifierKey: @(kProportionalNumbersSelector)
      }]
 };

UIFontDescriptor *const proportionalDescriptor = [existingDescriptor fontDescriptorByAddingAttributes: fontAttributes];
UIFont *const proportionalFont = [UIFont fontWithDescriptor: proportionalDescriptor size: [existingFont pointSize]];

You could add this as a category on UIFont if you wished, etc.

Edit note: thanks to Chris Schwerdt for the improvements.

Benjohn
  • 13,228
  • 9
  • 65
  • 127
  • 1
    _+1_ Don’t forget to import CoreText for those constants. – Tricertops Nov 14 '13 at 13:28
  • Thanks Martin, I'll amend the code to add that as it had me scratching my head initially! – Benjohn Nov 14 '13 at 14:49
  • This isn't working for me, every number except for a 1 seems to be proportional. Any ideas? – Tyler Pfaff Mar 13 '14 at 01:48
  • Could it just be the font you're using, @TylerPfaff? I've not checked the widths of each glyph, so I might be getting the same thing – it seems to _look_ okay though :-) – Benjohn Mar 13 '14 at 20:13
  • 2
    Values for the `UIFontFeatureTypeIdentifierKey` and `UIFontFeatureSelectorIdentifierKey` can be found in `SFNTLayoutTypes.h`, which further references [Apple's Font Registry documentation](https://developer.apple.com/fonts/registry/). – Chris Schwerdt Mar 15 '14 at 15:53
  • 2
    Also, you can remove the `kCharacterAlternativesType` key. I believe it was used in that time formatting example to replace the square colon character with a rounded colon character and doesn't affect the number spacing. – Chris Schwerdt Mar 15 '14 at 16:35
  • @ChrisSchwerdt Thanks. I'll edit the answer to include your suggestions, unless you'd prefer to and get the edit credit? – Benjohn Mar 17 '14 at 10:19
  • @TylerPfaff I think that for the Helvetica Neue font, even with proportional digits, it is just the "1" glyph that is narrower. This is still a great improvement over completely tabular digits, though. Other fonts that support proportional digits might be different. (p.s. – "_proportional_" is where glyph widths _do_ vary, "_tabular_" or "_fixed width_" or "_monospace_" is where they _don't_ vary). – Benjohn Oct 22 '14 at 09:57
  • 1
    This option only works if you're using a font which supports this feature. If you're using a custom font, this may not work. The options that a font supports can be inspected with something like this: `NSLog(@"%@", CTFontCopyFeatures ( ( __bridge CTFontRef ) myFont ));` – Anthony Mattox Mar 03 '15 at 14:28