Search code examples
swiftswiftuiuikitaccessibilityvariable-fonts

SwiftUI: Variable Dynamic Font


I'm in the process of upgrading my app from UIKit to SwiftUI and I've run into the issue of how to create a SwiftUI Text view which reacts to dynamic (accessible) font size and uses a variable font file.

In UIKit, we can create a UILabel view doing this using UIFontDescriptor:

public enum FontVariations: Int {
    case weight = 2003265652
}

let uiFontDescriptor = UIFontDescriptor(fontAttributes: [.name: "Inter", kCTFontVariationAttribute as UIFontDescriptor.AttributeName: [FontVariations.weight.rawValue: 400])
let customFont = UIFont(descriptor: uiFontDescriptor, size: 18)
let scaledFont = UIFontMetrics(forTextStyle: .body).scaledFont(for: customFont)

let label = UILabel()
label.font = scaledFont
label.adjustsFontForContentSizeCategory = true
label.text = "Hello, World!"

If we add this UILabel to a screen, we should see the variable font with weight 400 (regular), font size 18, and it should scale depending on the user's dynamic font size.

Since we're using variable fonts, we can feed in this UIFont using swift's Font initializer and try applying it to a Text view

Text("Hello, World!")
    .font(Font(scaledFont))

The issue is that this doesn't respond to the dynamic text size. Apple recommends doing

Text("Hello, World!")
    .font(.custom("Inter", size: 18, relativeTo: .body))

but this doesn't allow me to use a variable fonts with modified font axis'. I'm hoping to either make the Font(UIFont) initializer work with dynamic sizing or find another way to use variable fonts in SwiftUI that let me use the relativeTo initializer.


Solution

  • You can use ScaledMetric to make anything relative to a dynamic type:

    @ScaledMetric(relativeTo: .body) var dynamicSize = 18
    

    So you can use your custom UIFont with a dynamic size:

    Text("Hello World")
        .font(Font(customFont.withSize(dynamicSize)))