Set default custom font for entire app – Swift 5

There is no standard way of setting a custom font for an entire iOS application. The simplest standard way would be to subclass each view or override the default appearance of each control individually; which can be cumbersome.

Ideally, what I wanted was to set the font for the entire app – that includes navigation bars, labels, textfields, buttons, everything – with just one simple setting. Here’s how I did it.

This process involves overriding the default iOS methods, or method swizzling. Although it is a hack, there are apps active on the App Store which have implemented this, so you don’t need to worry about apps being rejected for this.

Here we will override the functions init(coder:), systemFont(ofSize:), boldSystemFont(ofSize:), italicSystemFont(ofSize:) with our custom methods.

This is a basic implementation, for only normal, bold, and italic system styles, but if you want more options you can override more methods bases on my this.

Add custom fonts to app

In your XCode project, create a folder and add your custom font files into it. Instead of loading them via property lists, I recommend using this library.

FontBlaster: https://github.com/ArtSabintsev/FontBlaster

Install this, and then in your AppDelegate.swift file, add FontBlaster’s blast() method in the didFinishLaunchingWithOptions method. This will programmatically load the fonts to your application.

Note that the actual names of the fonts to be used in the apps may be different from the font file names. FontBlasters blast() method has a completion handler that returns just that.     

FontBlaster.blast() { (fonts) in
  print(fonts) // fonts is an array of Strings containing font names
}

Run the app, and check your console. You will find the names of your custom fonts. Save them for the next step.

Now that you have the names, you can remove the completion handler, and use only one line FontBlaster.blast() for the app to load fonts.

func application(_ application: UIApplication, didFinishLaunchingWithOptions 
  launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    
    FontBlaster.blast()
}

Overriding default font methods

Now the actual code for switching the default font for our app.

Create a swift file Helper.swift and write the following. Don’t forget to replace the font name with the names of the fonts you are using.

struct AppFontName {
  static let regular = "ProximaNova-Regular"
  static let bold = "ProximaNova-Bold"
  static let lightAlt = "ProximaNovaA-Light"
}
//customise font
extension UIFontDescriptor.AttributeName {
  static let nsctFontUIUsage = UIFontDescriptor.AttributeName(rawValue: "NSCTFontUIUsageAttribute")
}

extension UIFont {

  @objc class func mySystemFont(ofSize size: CGFloat) -> UIFont {
        return UIFont(name: AppFontName.regular, size: size)!
  }

  @objc class func myBoldSystemFont(ofSize size: CGFloat) -> UIFont {
        return UIFont(name: AppFontName.bold, size: size)!
  }

  @objc class func myItalicSystemFont(ofSize size: CGFloat) -> UIFont {
        return UIFont(name: AppFontName.lightAlt, size: size)!
  }

  @objc convenience init(myCoder aDecoder: NSCoder) {
    guard
        let fontDescriptor = aDecoder.decodeObject(forKey: "UIFontDescriptor") as? UIFontDescriptor,
        let fontAttribute = fontDescriptor.fontAttributes[.nsctFontUIUsage] as? String else {
        self.init(myCoder: aDecoder)
        return
    }
    var fontName = ""
    switch fontAttribute {
    case "CTFontRegularUsage":
        fontName = AppFontName.regular
    case "CTFontEmphasizedUsage", "CTFontBoldUsage":
        fontName = AppFontName.bold
    case "CTFontObliqueUsage":
        fontName = AppFontName.lightAlt
    default:
        fontName = AppFontName.regular
    }
    self.init(name: fontName, size: fontDescriptor.pointSize)!
  }

  class func overrideInitialize() {
    guard self == UIFont.self else { return }

    if let systemFontMethod = class_getClassMethod(self, #selector(systemFont(ofSize:))),
        let mySystemFontMethod = class_getClassMethod(self, #selector(mySystemFont(ofSize:))) {
        method_exchangeImplementations(systemFontMethod, mySystemFontMethod)
    }

    if let boldSystemFontMethod = class_getClassMethod(self, #selector(boldSystemFont(ofSize:))),
        let myBoldSystemFontMethod = class_getClassMethod(self, #selector(myBoldSystemFont(ofSize:))) {
        method_exchangeImplementations(boldSystemFontMethod, myBoldSystemFontMethod)
    }

    if let italicSystemFontMethod = class_getClassMethod(self, #selector(italicSystemFont(ofSize:))),
        let myItalicSystemFontMethod = class_getClassMethod(self, #selector(myItalicSystemFont(ofSize:))) {
        method_exchangeImplementations(italicSystemFontMethod, myItalicSystemFontMethod)
    }

    if let initCoderMethod = class_getInstanceMethod(self, #selector(UIFontDescriptor.init(coder:))), // Trick to get over the lack of UIFont.init(coder:))
        let myInitCoderMethod = class_getInstanceMethod(self, #selector(UIFont.init(myCoder:))) {
        method_exchangeImplementations(initCoderMethod, myInitCoderMethod)
    }
  }
}

Set custom font for entire app

The last step – simply call a method on app launch to make the default font override happen.

Open AppDelegate.swift and add this.

override init() {     
    super.init()
    UIFont.overrideInitialize() 
}

Done! Run your app now, you should see your custom font everywhere now.


Also published on Medium.

By |2019-11-30T21:37:40+00:00November 30th, 2019|Categories: iOS, iOS Tutorials|Tags: |0 Comments

Leave A Comment