Some designs I receive from clients call for specific weight and family combinations for user interface fonts. Use a display font here, and a text font there. This is true of both custom fonts and system-provided fonts. Apple seems to encourage this level of finesse with system fonts in their online design resources. There are several distinct families in the downloadable San Francisco fonts available on the Apple developer site.
When requesting a system font in code, the UIFont returned will either be a display or a text font, depending on the API you use. For the
systemFont(ofSize:weight:) API, the cutoff is in the neighborhood of 22 points, above which you’ll get a display font and below which a text font. Other APIs like
preferredFont(forTextStyle:) might return a display or a text font regardless of point size. This is great for general purposes. An inexperienced designer can use whichever API is closest to their needs and receive a font whose family is a best-fit for that size, weight, and the characteristics of the current display.
However there are times when an experienced designer needs to specify not just a weight and a point size but also a family. In those situations, it is difficult for the developer to satisfy the design requirements. If you’re using a custom font bundled with your application, then you can create a font descriptor quite easily:
let descriptor = UIFontDescriptor(fontAttributes: [ UIFontDescriptorNameAttribute: MY_FONT_NAME ])
If you need to do this with the San Francisco fonts, it is much more difficult. There are only two ways I know of to do this.
You can make some educated guesses about the behavior of the UIFont APIs and do something like this:
let textFont = UIFont.systemFont(ofSize: 6) // .SFUIText 6pt, used for font name let titleFont = UIFont.preferredFont(forTextStyle: .title1) // .SFUIDisplay-Light 28pt, used for point size let descriptor = UIFontDescriptor(fontAttributes: [ UIFontDescriptorNameAttribute: textFont.fontName ]) let desiredFont = UIFont( descriptor: descriptor, size: titleFont.pointSize) // .SFUIText 28pt, combo of desired traits
Very Yucky Hack
A more reliable but even yuckier solution: you can bundle the downloadable fonts from the developer site, and reference them by name in a font descriptor:
let descriptor = UIFontDescriptor(fontAttributes: [ UIFontDescriptorFamilyAttribute: "SF UI Text", UIFontDescriptorFaceAttribute: "Semibold" ])
This bloats the size of your app bundle but at least you can guarantee that you’re always using a display or text font when you need one or the other.
Why Does This Matter?
To illustrate why this matters, please consider the following example. While the anecdote below is partially contrived, it’s representative of the kind of design problems I’ve had to solve on actual projects. I think it serves as a useful illustration of the shortcomings of the current UIFont APIs.
Consider a tvOS app with a horizontal collection view of cards. As each card takes the center spot, it receives focus and enlarges to about twice its unfocused size. Each card contains some body text.
Lets assume for the sake of this example that a variation of
SF UI Display will be vended from whichever system font API is used to request a 44 point regular weight font.
Which font family would actually be preferable for the text on these cards? The answer is not straightforward. In the enlarged state, a display font might make sense. At a size of 44 points in the default expanded card state, a text font might look awkward in comparison. A 44 point size is probably large enough to accommodate the characteristics of a display font:
But on the other hand, for every one focused card there are always four unfocused cards (two on either side) visible at all times. If the unfocused card contents are scaled down via a scale transform, then whatever font is used at the focused size (with an identity transform) will be scaled down to a perceived “22 point” size. Note that the text is still laid out using a 44 point display font, but the unfocused card transform results in a perceived size that is much smaller. At this scaled down size, a display font would be harder to read, for example in words like “clipper” or “sail” where lowercase L’s are adjacent to lowercase I’s — especially so when staring across the room at a grainy television.
So what alternatives are there?
One alternative would be to request two fonts: one to layout the text in the focused state, and another when laying out the unfocused state. The problem here is that if this results in a display font at the 44 point size and a text font at the 22 point size, the line fragments will most likely break at different words between the two states, creating confusion for the user during focus update animations:
The more desirable alternative for this design is to always prefer a text font, since this preserves the maximum amount of legibility at all focus states:
We would use a scale transform to downscale the 44-point identity-transformed text to a perceived 22-point size, preserving legibility in the both the focused/enlarged state and the unfocused/scaled-down state, and without disturbing the line breaks during the transition from one to the other.
Wishlist For Apple
If anyone from Apple is reading, I’d love if ya’ll can add programmatic access to family-based font selection of system fonts:
let bigTextFont = UIFont.systemFont( ofSize: 44, weight: UIFontWeightRegular, family: UIFontFamilyText )