OSX Screensavers and Custom Fonts


Our teammate Sonny has recently been upgrading our online world, by building really sweet slackbots. So, when he asked if I wanted to help spruce up our conference rooms with a custom screensaver, I had to get in on it. Sonny had already set up a Swift project to generate our .saver file, and so we started adding background views, timers and a text view for displaying the time. I wanted to make sure that our text view used the same font face that appears across all of our materials, and started the usual process for adding a custom font:

  • dragging the .otf (or .ttf) file into the project, selecting the correct build target and checking the “Copy items if needed” box when prompted;
  • making sure that the file appears in the “Copy Bundle Resources” list under the project’s Build Phases; and,
  • adding the Fonts provided by application key to the info.plist file, and adding the custom font to the array of provided fonts.

But when I printed out the available fonts (more on this below)… the custom font was nowhere to be found! ?

After checking my steps, it became clear that the normal process wasn’t the right one. A little bit of searching revealed that the last step in the process wasn’t doing what I’d expected. Apparently,  Fonts provided by application  only works if you have… an application. A screensaver is really just a fancy view, wrapped up so that mac’s screen saver application is able to render it.

In most of the applications that I’ve worked on, the text rendering provided by UIKit/AppKit has been sufficient. However, there is an underlying text-rendering framework called CoreText (it’s the typographical counterpart to CoreGraphics), that we’ll need to use in this case. CoreText has a CTFontManagerRegisterFontsForURL function that allows you to pass in the font file’s path, a CTFontManagerScope (which sets rules around the availability of the font, we’ve used .process which makes it available for as long as the screensaver is running), and an error pointer (which, in the event of an error, will contain the error message). You can call this anywhere in a project.

func registerCustomFonts() {
        let paths = Bundle.main.paths(forResourcesOfType: "otf", inDirectory: "")
        for path in paths {
            let fontUrl = NSURL(fileURLWithPath: path)
            var errorRef: Unmanaged<CFError>?
            
            let success = CTFontManagerRegisterFontsForURL(fontUrl, .process, &errorRef)
            
            if (errorRef != nil) {
                let error = errorRef!.takeRetainedValue()
                print("Error registering custom font: \(error)")
            }
            print(success)
        }
}

Once the `CTFontManager` has registered the font, it becomes available as an NSFont within your project… provided you know your font’s correct name.  While snippets are readily available for logging UIFont names by family, things are a little different in Cocoa apps.  You can view the names by creating a shared instance of an `NSFontManager` and printing its `.availableFonts` property.  For a pretty print output, you can use the following:

func printFamilyNames() {
    let fontManager = NSFontManager.shared()
    for family in fontManager.availableFontFamilies {
        if let fonts = fontManager.availableMembers(ofFontFamily: family) {
            for font in fonts{
                print(font)
            }
        }
    }
}

This is a little detail for a somewhat uncommon issue, but if you’re in need of a special screensaver font, I hope this helps!

Bonus tip: In the process of researching this, I also learned that as part of this font registration process, you can encrypt fonts that have licensing limitations on their distribution. 

DevMynd – custom software development services with practice areas in digital strategy, human-centered design, UI/UX, and mobile and web application development.

Erin is a member of DevMynd’s software engineering team focusing on mobile apps and web development. She has been with the company since 2013.