Henrik Panhans

iOS Developer & CS Student

Dynamic type with custom font weight - QuickSwift

25. November 2019

Hello again, long time no see. I've been pretty occupied with work and university the last couple months so I didn't really get the chance to write any articles. But I do want to share some of the stuff I have been working on so here I am with a new format I'll call 'QuickSwift', as the name suggests, the articles will be pretty short and with lots of code examples. The focus is not going to be on explaning every little thing that is going on but rather to just share some code that I've written along with some explanation and reasoning. The goal is to serve more as an inspiration to try that code yourself and understand the semantics yourself - because frankly I'm a lazy writer and I believe in learning-by-doing (that's how I learned Swift after all).

What is Dynamic Type?

Dynamic Type is an awesome feature in UIKit that Apple introduced way back in iOS 7 and it allows you to set a 'base-font' on a UILabel, UITextView and so forth. All you have to do to support it is to create a new font and specify a style.

swift
label.font = UIFont.preferredFont(forTextStyle: .body)
label.adjustsFontForContentSizeCategory = true

That's it, pretty easy right? There are a bunch of pre-defined text styles such as body, title1, callout and lots more. Most of them use the system font at regular weight with different base point sizes that also scale differently based on the device's setting.

Custom Fonts

You can also use your own fonts with dynamic type. To do so you need to create your font at the point size of your desire and then create UIFontMetrics for the style you want it to adhere to.

swift
guard let customFont = UIFont(name: "CustomFont-Light", size: UIFont.labelFontSize) else {
    fatalError("You screwed something up dude")
}
label.font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: customFont)
label.adjustsFontForContentSizeCategory = true

That is also pretty easy and straight-forward (at this point you might be wondering why you've even read this far, don't worry we're getting there).

System Font with custom weight

My goal for the app I'm currently working on was to get title1 style and scaling, but I wanted the text to be semi-bold instead of regular weight. So how do we do this? Here's the approach I ended up with.

swift
public extension UIFont.TextStyle {
    /// Returns a non-scaling font for the specified text style
    /// at system default settings
    var baseFont: UIFont {
        return UIFont.systemFont(ofSize: defaultPointSize, weight: defaultWeight)
    }

    /// Returns the a non-scaling font with the point size of the current text
    /// style and the specified weight
    func withWeight(_ weight: UIFont.Weight) -> UIFont {
        return UIFont.systemFont(ofSize: defaultPointSize, weight: weight)
    }
		
    /// Returns the default weight for the current text style
    /// at system default settings
    var defaultWeight: UIFont.Weight {
        switch self {
        case .headline:
            return .semibold
        default:
            return .regular
        }
    }

    /// Returns the default point size for the current text style
    /// at system default settings
    var defaultPointSize: CGFloat {
        switch self {
        case .largeTitle:
            return 34
        case .title1:
            return 28
        case .title2:
            return 22
        case .title3:
            return 20
        case .headline:
            return 17
        case .body:
            return 17
        case .callout:
            return 16
        case .subheadline:
            return 15
        case .footnote:
            return 13
        case .caption1:
            return 12
        case .caption2:
            return 11
        default:
            return 17
        }
    }
}

Great, now that we have direct access to all the base values, all that's left to do is to create an extension to UIFont to conveniently make use of our custom scaling fonts. We can do so by creating UIFontMetrics for the specified style and then simply applying it to the associated base font (with custom weight).

swift
public extension UIFont {
    static func preferredFont(forTextStyle style: UIFont.TextStyle, weight: UIFont.Weight) -> UIFont {
        let font = style.withWeight(weight)
        return UIFontMetrics(forTextStyle: style).scaledFont(for: font)
    }
}

This gives us access to a new API call of the form UIFont.preferredFont(forTextStyle: .body, weight: .bold) which is very similar to the Apple provided one.

There you go, we're done. Thank you for reading, and feel free to roast me in the comments for writing ugly code or whatever other strong feelings you might have about this article.

via GIPHY