May 16, 2020 • 4 min read
Building a Design System for iOS - Part 2 - Typography
This is part 2 of the Building a Design System for iOS series. In case you missed the introduction on design system, do give it a read.
This post will take on the "Typography" component of the design system. To kick things off, let's take a real use case scenario. I've done quick mockup for a podcasts app.
Disclaimer - I'm not a UI designer, so please bare with my design skills.
Let's break down the typography of the app. As you can see, there's 4-5 styles of titles going on there. There's 2 styles for regular body text. A couple of them for button texts. I've chosen "AvenirNext" font. Here's the exhaustive list -
Let's start coding this right away. Enum
is an good start to list the types of Font
in the app.
enum Font: String {
case regular = "AvenirNext-Regular"
case demiBold = "AvenirNext-DemiBold"
case medium = "AvenirNext-Medium"
case heavy = "AvenirNext-Heavy"
var name: String {
return self.rawValue
}
}
Now if we look at the fonts list in the design system, the way a text element looks is defined by the font type and the font size. Perfect, looks like UIFont
provides exactly what we want, easy right? Well, almost. If we plan to have our app accessible, we need to support dynamic font sizes.
Dynamic Type
In iOS 11, Apple brought in support to automatically scale fonts. All you had to do was mention the textStyle
and enable adjustsFontForContentSizeCategory
. That is,
label.font = UIFont.preferredFont(forTextStyle: .body)
label.adjustsFontForContentSizeCategory = true
This works out of the box for default system fonts. For custom fonts, it needed a little bit of work. Apple has a nice documentation to do this using UIFontMetrics
. So basically, the code to do that looks like this -
guard let customFont = UIFont(name: "MyFontName", size: UIFont.labelFontSize) else {
fatalError("Failed to load the "MyFontName" font.")
}
label.font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: customFont)
label.adjustsFontForContentSizeCategory = true
Putting it together
In order to scale our custom fonts, we need to define a UIFont.TextStyle
in them. So for our design system, a text element will be defined by 3 properties - font type, size and text style. UIFontDescriptor
seems to be good candidate to fit in all the values. But I found it to be an overkill for this purpose. Instead let's define our own type to host these values. I'm going to call it something unique, say a FontDescription
😂
struct FontDescription {
let font: Font
let size: CGFloat
let style: UIFont.TextStyle
}
Now that we have the foundation to describe our style, let's translate the list of styles to code. I've gone ahead and named the styles semantically -
As always, let's create an Enum
TextStyle
-
enum TextStyle {
case display1 //32pt, Demibold
case display2 //28pt, Demibold
case display3 //20pt, Demibold
case display4 //16pt, Demibold
case display5 //14pt, Demibold
case paragraph //16pt, Regular
case paragraphSmall //14pt, Regular
case link //16pt, Demibold
case buttonBig //16pt, Medium
case buttonSmall //14pt, Medium
}
Let's create a property fontDescription: FontDescription
for our TextStyle
-
extension TextStyle {
private var fontDescription: FontDescription {
switch self {
case .display1:
return FontDescription(font: .demiBold, size: 32, style: .largeTitle)
case .display2:
return FontDescription(font: .demiBold, size: 28, style: .title1)
case .display3:
return FontDescription(font: .demiBold, size: 20, style: .title2)
case .display4:
return FontDescription(font: .demiBold, size: 16, style: .headline)
case .display5:
return FontDescription(font: .demiBold, size: 14, style: .subheadline)
case .paragraph:
return FontDescription(font: .regular, size: 16, style: .body)
case .paragraphSmall:
return FontDescription(font: .regular, size: 14, style: .caption1)
case .link:
return FontDescription(font: .demiBold, size: 16, style: .callout)
case .buttonBig:
return FontDescription(font: .medium, size: 16, style: .callout)
case .buttonSmall:
return FontDescription(font: .medium, size: 14, style: .callout)
}
}
}
Now that we have all the pieces in code, we are ready to define a font: UIFont
property for TextStyle
extension TextStyle {
var font: UIFont {
guard let font = UIFont(name: fontDescription.font.name, size: fontDescription.size) else {
return UIFont.preferredFont(forTextStyle: fontDescription.style)
}
let fontMetrics = UIFontMetrics(forTextStyle: fontDescription.style)
return fontMetrics.scaledFont(for: font)
}
}
Setting a font to a label looks like this -
label.font = TextStyle.buttonBig.font
Neat. That's it for this part. Next up, Colors
This is part two in a series of blog posts highlighting my experience in building different components of a design system for iOS. In case you want to jump to other parts -