NEW: Join my free 100 Days of SwiftUI challenge today! >>

How to use Dynamic Type with a custom font

Paul Hudson    @twostraws   

Fully updated for Xcode 11.2

SwiftUI comes with support for all of Dynamic Type’s font sizes, all set using the .font() modifier. However, if you ask for a specific font and size, you’ll find your text no longer scales up or down automatically according to the user’s Dynamic Type settings – it remains fixed.

To work around this we need to create a custom ViewModifier that can scale up our font size based on the current accessibility setting, and also detect when that setting changes.

I’m going to give you the code first, then walk through how it works and why:

@available(iOS 13, macCatalyst 13, tvOS 13, watchOS 6, *)
struct ScaledFont: ViewModifier {
    @Environment(\.sizeCategory) var sizeCategory
    var name: String
    var size: CGFloat

    func body(content: Content) -> some View {
       let scaledSize = UIFontMetrics.default.scaledValue(for: size)
        return content.font(.custom(name, size: scaledSize))
    }
}

@available(iOS 13, macCatalyst 13, tvOS 13, watchOS 6, *)
extension View {
    func scaledFont(name: String, size: CGFloat) -> some View {
        return self.modifier(ScaledFont(name: name, size: size))
    }
}

That’s all the code required to get custom fonts working with Dynamic Type. As an example of using it, here’s a list with two text views, one using the built-in font and one with a scalable Georgia font:

struct ContentView: View {
    var body: some View {
        List {
            Text("Hello World")
            Text("Hello World")
                .scaledFont(name: "Georgia", size: 12)
        }
    }
}

Now you’ve seen how it works, let’s look at why it works.

First, we have this custom view modifier:

struct ScaledFont: ViewModifier {
    @Environment(\.sizeCategory) var sizeCategory
    var name: String
    var size: CGFloat

    func body(content: Content) -> some View {
       let scaledSize = UIFontMetrics.default.scaledValue(for: size)
        return content.font(.custom(name, size: scaledSize))
    }
}

That accepts a name and size for our font, then uses UIFontMetrics to scale up the requested font to whatever matches the user’s current device setting, and send it back.

We then wrap that inside an extension on View to make it easier to use:

@available(iOS 13, macCatalyst 13, tvOS 13, watchOS 6, *)
extension View {
    func scaledFont(name: String, size: CGFloat) -> some View {
        return self.modifier(ScaledFont(name: name, size: size))
    }
}

All that does is wrap up the call to our custom font modifier so that it looks nicer in our views – it means we write .scaledFont(name: "Georgia", size: 12) to use it, rather than .modifier(ScaledFont(name: "Georgia", size: 12)).

Now, you might wonder why we need the custom view modifier if all we do is pass on the data. Well, the clue lies in this line in our view modifier:

@Environment(\.sizeCategory) var sizeCategory

That asks the system to provide the current size category from the environment, which determines what level Dynamic Type is set to. The trick is that we don’t actually use it – we don’t care what the Dynamic Type setting is, but by asking the system to update us when it changes our UIFontMetrics code will be run at the same time, causing our font to scale correctly.

Tip: The UIFontMetrics class is not available on macOS, which is why I’ve added the @available markers.

SPONSORED Instabug helps you identify and resolve severe crashes quickly. You can retrace in-app events and know exactly which line of code caused the crash along with environment details, network logs, repro steps, and the session profiler. Ask more questions or keep users up-to-date with in-app replies straight from your dashboard. Instabug takes data privacy seriously, so no one sees your data but you! See more detailed features comparison and try Instabug's crash reporting SDK for free.

Similar solutions…

BUY OUR BOOKS
Buy Pro Swift Buy Swift Design Patterns Buy Testing Swift Buy Hacking with iOS Buy Swift Coding Challenges Buy Swift on Sundays Volume One Buy Server-Side Swift (Vapor Edition) Buy Advanced iOS Volume One Buy Advanced iOS Volume Two Buy Advanced iOS Volume Three Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with macOS Buy Dive Into SpriteKit Buy Swift in Sixty Seconds Buy Objective-C for Swift Developers Buy Server-Side Swift (Kitura Edition) Buy Beyond Code

Was this page useful? Let us know!

Average rating: 5.0/5