I'm playing around with SwiftUI, and want to use a custom UI font for my project. However, I don't want to lose the dynamic type resizing that comes with the built-in font classes (e.g. Large Title).
Apple does provide a custom font modifier for Text
:
Text("Hello, world!")
.font(.custom("Papyrus", size: 17))
However, this fixes the size to 17pt. When you run this on a device or in the Simulator and open the Accessibility Inspector to adjust the OS-level font size, the Text
element does not update.
The size:
parameter is not optional, so you must pass in something. And unfortunately, you can't get the size
of an existing font (even a custom one), because Font
does not have a size
parameter.
It seems to be a common pattern in the rest of SwiftUI that parameters can either be optional, or you can pass in nil
to explicitly disable certain behavior. I would expect the size:
parameter on .custom()
to be optional, and internally either use the size from a previous Font
modifier, or to use the default size set by Text
.
Alternately, the static methods that define system styles (e.g. .largeTitle
) could accept an argument that provides a custom font name: .largeTitle("Papyrus")
Does anyone have a workaround?
I stumbled upon a nice way to achieve this also via ViewModifier
. I borrowed the base modifier from this Hacking With Swift's article on Dynamic Type and Custom Fonts. Here's the result:
import SwiftUI
@available(iOS 13, macCatalyst 13, tvOS 13, watchOS 6, *)
struct CustomFont: ViewModifier {
@Environment(\.sizeCategory) var sizeCategory
var name: String
var style: UIFont.TextStyle
var weight: Font.Weight = .regular
func body(content: Content) -> some View {
return content.font(Font.custom(
name,
size: UIFont.preferredFont(forTextStyle: style).pointSize)
.weight(weight))
}
}
@available(iOS 13, macCatalyst 13, tvOS 13, watchOS 6, *)
extension View {
func customFont(
name: String,
style: UIFont.TextStyle,
weight: Font.Weight = .regular) -> some View {
return self.modifier(CustomFont(name: name, style: style, weight: weight))
}
}
And usage:
Text("Hello World!")
.customFont(name: "Georgia", style: .headline, weight: .bold)
This way you can stick to bundled Text Styles without needing to provide sizes explicitly. Should you want to do so, the font
modifier already allow us to, and the scaling could be handled through one of the alternative approaches given to this question.
Also, please note that because the styling is applied within a ViewModifier
conformant struct
, which in turn responds to changes to the environment's sizeCategory
, the views will reflect changes to the accessibility settings right upon switching back to your app; so there's no need to restart it.