iosnsstringuifontuifontdescriptor

Displaying proportionally spaced numbers (instead of monospace / tabular) on iOS


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.


Solution

  • 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.