How to combine Dynamic Type and monospaced font

This is a small tip for cases with countdown labels and similar UI.

Published: Dec. 16, 2022
App Store

Have you ever had a label in your app that showed a live countdown? And have you noticed it to subtly shift horizontally when the countdown value changed?

This is because, by default, numbers have different widths. 1 takes less horizontal space than 5, for example. And this can easily affect the total width of the label.

The basic solution is quite simple, thanks to UIFont.monospacedDigitSystemFont method. You give it a size and weight, and it will return system font with all digits being the same width. And the horizontal shift is gone.

What about Dynamic Type?

I don’t need to repeat that having Dynamic Type support in your app is important. However, this gets tricky with monospaced fonts because there is no built-in method to give you monospaced font for the text styles.

Fortunately, we can fix this with the UIFontMetricts and UIFontDescriptor types and wrap it all in a neat little extension on UIFont.

The result looks like this:

import UIKit

extension UIFont {
    static func preferredMonospacedFont(for style: TextStyle, weight: Weight) -> UIFont {
        let metrics = UIFontMetrics(forTextStyle: style)
        let desc = UIFontDescriptor.preferredFontDescriptor(withTextStyle: style)
        let font = UIFont.monospacedDigitSystemFont(ofSize: desc.pointSize, weight: weight)
        return metrics.scaledFont(for: font)
    }
}

And we are done!

Usage then looks something like this:

let countdownLabel = UILabel()
countdownLabel.font = .preferredMonospacedFont(for: .subheadline, weight: .bold)
Bluesky logo

Follow on Bluesky to not miss new posts

Filip Němeček profile photo

WRITTEN BY

Filip Němeček Mastodon

iOS blogger and developer with interest in Python/Django.

iOS blogger and developer with interest in Python/Django.