RS-logoRam Shandilya

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.

Podcasts app design

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 -

typography

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 -

style-names

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 -