Not all font instances are created equally! In iOS text is mostly displayed using proportional fonts, meaning each character width is trimmed and varies depending on each character. This makes the text easier to read and feel more natural and you’ll notice this on characters such as ‘i’ which will often be the thinnest character compared to say an ‘m’ character. However for numbers displayed in a tabular format such as times, figures and currencies you’ll want monospaced characters so you can tidy up the layout and visually scan the data quickly. So how do you do this?
The easy way to do it is to leverage the powerful font features hidden within most fonts. Something to remember is that not all fonts have these features but if you contact the font publisher they’ll probably have an updated file with these features in.
First you’ll need to use a UIFontDescriptor. This is a class for describing a font and it’s properties in a way that you can list and modify them. Once you’re done you can convert them into a UIFont instance very quickly for using in UILabels and anything else that needs a UIFont. In this example I create a category for `UIFont` and add the font properties for using monospaced numbers. Finally I create a font instance by passing the descriptor into the font initialiser.
extension UIFont {
// See https://richardwarrender.com/2015/06/beautifully-formatted-times-and-numbers/ for more details
func withTabularNumbers() -> UIFont {
let featureArray: [[UIFontDescriptor.FeatureKey: Any]] = [
[
.featureIdentifier: kNumberSpacingType,
.typeIdentifier: kMonospacedNumbersSelector
]
]
let newFontDesc = self.fontDescriptor.addingAttributes(
[.featureSettings: featureArray]
)
return UIFont(descriptor: newFontDesc, size: newFontDesc.pointSize)
}
}
That makes numbers line up but how can we format times better? The secret is in the ‘:’ character. Normally this would be proportional too but we can use a font descriptor setting attribute to ensure it uses an alternative type as below.
extension UIFont {
func withTabularNumbersAndTimes() -> UIFont {
let featureArray: [[UIFontDescriptor.FeatureKey: Any]] = [
[
.featureIdentifier: kNumberSpacingType,
.typeIdentifier: kMonospacedNumbersSelector
],
[
.featureIdentifier: kCharacterAlternativesType,
.typeIdentifier: 1
]
]
let newFontDesc = self.fontDescriptor.addingAttributes(
[.featureSettings: featureArray]
)
return UIFont(descriptor: newFontDesc, size: newFontDesc.pointSize)
}
}
This now lets prices and other numbers display in a way that is more visually pleasing and easier to scan when laid out next to each other.