What can go wrong when using custom fonts in SwiftUI
Ensure that font weight gets correctly applied even if a custom font was not registered by the app developer

This blog post is targeting SDK developers who bundle custom fonts in their package/library/framework and these fonts shall be used in reusable SwiftUI views.
I will explain an issue observable when using font weight related view modifiers (like bold) on a non-registered custom font.
Let's assume you added the Open Sans true type font files to your bundle
- OpenSans-Regular.ttf
- OpenSans-Bold.ttf
- ...
You also provide reusable SwiftUI views which shall automatically leverage the custom font. To do so you can pass the result of Font.custom(:size) to the font view modifier.
struct ReusableView: View {
var body: some View {
Text("Use Font.custom directly")
.font(.custom("OpenSans-Regular", size: 17))
.bold()
}
}
If you use the bold view modifier then SwiftUI is smart enough to load OpenSans-Bold.

This all works well as long as the app developer did not forget to register the font in the app for use.
Font.registerOpenSans()
extension Font {
static func registerOpenSans() {
self.register(name: "OpenSans-Regular", withExtension: "ttf")
self.register(name: "OpenSans-Bold", withExtension: "ttf")
// ...
}
static func register(name: String, withExtension: String) {
let fontURL = Bundle.main.url(forResource: name, withExtension: withExtension)!
var error: Unmanaged<CFError>?
CTFontManagerRegisterFontsForURL(fontURL as CFURL, .process, &error)
if let error = error {
print(error.takeUnretainedValue())
}
}
}
So what if the app developer forgot to register the font?

You might expected that the bold font weight would still gets applied. But this is not the case. Apple Bug ?
To ensure that the font weight gets applied, no matter what, I recommend to use an extension. This extension either loads the font or fallbacks to the system font.
extension Font {
/// get "Open Sans" font or fallbacks to the system font in case the custom font was not registered
/// - Parameter size: size of font
/// - Returns: "Open Sans" font (or sytem font as fallback)
static func openSans(size: CGFloat) -> Font {
guard UIFont.familyNames.contains("Open Sans") else {
return Font.system(size: size)
}
return .custom("OpenSans-Regular", size: size)
}
}
This will ensure that SwiftUI will be able to apply font weight even to a fallback font.

struct ReusableView: View {
var body: some View {
VStack(alignment: .leading) {
Text("Use extension with fallback handling")
.font(.openSans(size: 17))
.bold()
Text("Use Font.custom directly")
.font(.custom("OpenSans-Regular", size: 17))
.bold()
}
}
}
Of course the result is the same in case the font was registered.

About the author
I am a Software Engineer working on open source and enterprise mobile SDKs for iOS and MacOS developers written in Swift. From 馃嚛馃嚜 and happily living in 馃嚭馃嚫
Comments
Join the discussionNo comments yet. Be the first to comment.



