I’m in the midst of this weird 80s jag, and I was born the following decade. Go figure…

Swift: Custom Fonts. Slightly Less Awful.

I’m giving you a Nightcall, to tell you how I feel.

We’ve probably all been there. I know I have.

The Sketch file — or equivalent — has arrived from the design team, and contained within it is a mass of custom fonts that absolutely must be used.

Neatly managing custom fonts is a complete pain. Even if you only have one deviant font, having to plonk UIFont constructors all over your code is indescribably awful. Trying to come up with a cleaner solution is also awful, because it usually ends up just as unmanageable by the end of the project as the last time you tried. Or maybe that’s just me.

Five times now, I have tried to develop a robust and reusable solution to this problem. Four times I have failed to do so. Last week, I think I finally cornered an acceptable — dare I say reusable — solution, and that’s why I’m writing this post today.


Actually getting custom fonts to physically work in an iOS project is almost a piece of cake. Simply copy the files in — for pete’s sake, check the licence — and add the filenames to the Info.plist “Fonts Provided by Application” key. Also remember to check that they’re added to the target, and that they’re bundled as Resources in your app.

Getting them working well is a whole other can of worms. The default system font on iOS has been specially developed to be perfectly readable on the displays it calls home. This is not so for every font available, and luckily any design team with the slightest bit of sense about them will pay attention to this. It’s almost as if UIFont doesn’t want you to use custom fonts either, because you’re forced to use it like so:

something.font = UIFont(name: "SuperAwesomeFont-SemiboldItalic", size: 16.83")

Ew.


Whenever you invoke the above, you’ll need to have the font’s PostScript name handy, otherwise the system default gets used as a fallback. Depending on the alternative in question, the result of this mistake can sometimes appear correct. However, an eagle-eyed designer can spot this kind of difference quicker than blinking — so make sure you take a note of them!

One frustrating thing I’ve noticed of late, is that font sizes in Sketch often do not translate precisely from canvas to device. Being a designer by moonlight, I suppose I should’ve expected it, but I’ve had more Jira cards — with notes concerning — bumped back from QA than I have hairs on my head. Laboriously trawling through the code and minutely altering the font everywhere has since become a pet peeve of mine.

So frustrations abound, I set about fixing it.

A Constants File On Project Street

Admit it, you’ve either got one or you’ve used one. Almost every iOS dev I’ve met has used a Constants file at some point. And for certain applications (ha!), it’s a useful solution that isn’t over-engineered. For fonts, you’d typically have it setup like so:

struct Fonts {
static let Regular = UIFont(name: “Font-Regular”, size: 14)
}

…and repeat as needed for variations in weight. Invoking a font declared this way usually looks like this:

something.font = Fonts.Regular

And there’s nothing at all wrong with that. However imagine you suddenly needed another variant or three of “Font-Regular” at different sizes only. Suddenly you wind up managing a complete nightmare like this:

struct Fonts {
static let Regular = UIFont(name: “Font-Regular”, size: 14)
static let Regular16 = UIFont(name: “Font-Regular”, size: 16)
static let Regular20 = UIFont(name: “Font-Regular”, size: 20)
static let RegularCallDrPhilThisIsTooBig = UIFont(name: “Font-Regular”, size: 72)
}

Imagine something like that for a dozen font weights? Ugh! We can make this better!


When Category Met Extension

Extensions are to Swift what categories are to Objective-C. They are more aptly named in Swift however, as they do what they say on the tin — they extend classes with extra functionality.

Let’s go ahead and extend our old friend UIFont with our custom font.

extension UIFont {
class func regular(ofSize size: CGFloat) -> UIFont {
return UIFont(name: “Font-Regular”, size: size)
}
}

With this approach, simply add more functions to your extension for each weight required, this time eliminating duplicates of the same font weight but keeping the flexibility of size. Or we can turn the notch up a tad, and make it slightly more flexible.


The Font Book Club

This time, we’re going to be using a Swift enum to solve our problem. For the uninitiated, enum’s in Swift may — at first glance — look like a slightly improved variant of the well known C enum. However, a closer look reveals that Swift’s enums are in-fact quite different, and that the design decisions made allow them to be used in a wider range of practical scenarios. In particular, I find them a great way to clearly state the intention of my code.

enum FontBook: String {
case Regular = "Avenir"
case HeavyItalic = "AvenirNext-HeavyItalic"
  func of(size: CGFloat) -> UIFont {
return UIFont(name: self.rawValue, size: size)!
}
}

This lone enum can hold all the fonts you will ever need to use in your application. You simply need to add cases for each font you want to use, give them the appropriate PostScript values, and invoke them like so:

something.font = FontBook.Regular.of(1024)

Simple huh? Feel free to rename FontBook to Fonts or something, if it CGFloats your boat.

Let’s Make It Dynamic!

To support dynamic fonts, you’ll need to make some changes.

enum DynamicType : String {
case Body = "UIFontTextStyleBody"
case Headline = "UIFontTextStyleHeadline"
case Subheadline = "UIFontTextStyleSubheadline"
case Footnote = "UIFontTextStyleFootnote"
case Caption1 = "UIFontTextStyleCaption1"
case Caption2 = "UIFontTextStyleCaption2"
}
enum FontBook: String {
case Regular = "Avenir"
case HeavyItalic = "AvenirNext-HeavyItalic"
  func of(style: DynamicType) -> UIFont {
let preferred = UIFont.preferredFontForTextStyle(style.rawValue).pointSize
return UIFont(name: self.rawValue, size: preferred)!
}
}

Tidbit:

A brilliant thing about Swift enums is the ability to give one a raw type, and if no explicit associated value is given to a case, the case name itself becomes the rawValue. To the best of my knowledge, this only works with Strings.

We declared a DynamicType string enum, which holds all the variants of UIFontTextStyle. It beats having to type it out, and we still keep our single function. Only now that function has a different parameter which takes the style of dynamic type you want to use. Its usage hasn’t changed all that much:

something.font = FontBook.Regular.of(.Body)

I opted to not use the tidbit I detailed above on this enum, because having a statement like this feels far too long:

something.font = FontBook.Regular.of(.UIFontTextStyleBody)

But each to their own. Please let me know of any ways that you would improve this.

If you liked this post, please consider recommending it. I notice each one and appreciate them quite a lot. Thank-you!

About Us

At jtribe, we proudly craft software solutions for iOS, Android and Web and are passionate about our work. We’ve been working with the iOS and Android platforms since day one, and are one of the most experienced mobile development teams in Australia. We measure success on the impact we have, and with over six-million end users we know our work is meaningful, and this continues to be our driving force.
Like what you read? Give Ben Dietzkis a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.