Changing all UILabel fonts in app depending on current font weight using Swift 4

Viewed 3483

In Swift 3, I used the following to replace all fonts in BOLD to MEDIUM in AppDelegate:

UILabel.appearance().substituteFontNameBold = "HelveticaNeue-Medium"

And by adding this extension to UILabel :

extension UILabel {
    var substituteFontNameBold : String {
        get { return self.font.fontName }
        set {
            if self.font.fontName.contains("Bold") {
                self.font = UIFont(name: newValue, size: self.font.pointSize)
            }
        }
    }
}

With the upgrade to Swift 4, I get a "fatal error: unexpectedly found nil while unwrapping an Optional value" on

if self.font.fontName.contains("Bold") {

How can I continue to replace all fonts in BOLD to MEDIUM in labels with Swift 4?

3 Answers

Swift 4.0

This will help you set custom font for all of your UILabel, UIButton, etc. Simply create a extension and replace with this.

var appFontName       = "Gudea" //Please put your REGULAR font name
var appFontBoldName   = "Gudea" //Please put your BOLD font name
var appFontItalicName = "Gudea" //Please put your ITALIC font name

extension UIFont {

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

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

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

@objc convenience init(myCoder aDecoder: NSCoder) {
    if let fontDescriptor = aDecoder.decodeObject(forKey: "UIFontDescriptor") as? UIFontDescriptor {
        if let fontAttribute = fontDescriptor.fontAttributes[UIFontDescriptor.AttributeName(rawValue: "NSCTFontUIUsageAttribute")] as? String {
            var fontName = ""
            switch fontAttribute {
            case "CTFontRegularUsage":
                fontName = appFontName
            case "CTFontEmphasizedUsage", "CTFontBoldUsage":
                fontName = appFontBoldName
            case "CTFontObliqueUsage":
                fontName = appFontItalicName
            default:
                fontName = appFontName
            }
            self.init(name: fontName, size: fontDescriptor.pointSize)!
        }
        else {
            self.init(myCoder: aDecoder)
        }
    }
    else {
        self.init(myCoder: aDecoder)
    }
}

class func overrideInitialize() {
    if self == UIFont.self {
        let systemFontMethod = class_getClassMethod(self, #selector(systemFont(ofSize:)))
        let mySystemFontMethod = class_getClassMethod(self, #selector(mySystemFont(ofSize:)))
        method_exchangeImplementations(systemFontMethod!, mySystemFontMethod!)

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

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

        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!)
    }
}
}

Update: Swift3.2

replace this line:

if let fontAttribute = fontDescriptor.fontAttributes[UIFontDescriptor.AttributeName(rawValue: "NSCTFontUIUsageAttribute")] as? String {

with:

if let fontAttribute = fontDescriptor.fontAttributes["NSCTFontUIUsageAttribute"] as? String {

I have found the fix for this, you basically have to add @objc to make it works, after added it no more nil value

@objc public var substituteFontName : String

This can be done by overriding the system font as follow:

import UIKit

struct AppFontName {
    static let regular = "DINNextLTArabic-Regular"
}

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 convenience init(myCoder aDecoder: NSCoder) {
        if let fontDescriptor = aDecoder.decodeObject(forKey: "UIFontDescriptor") as? UIFontDescriptor {
                let fontName = AppFontName.regular
                self.init(name: fontName, size: fontDescriptor.pointSize)!

        } else {
            self.init(myCoder: aDecoder)
        }
    }

    class func overrideInitialize() {
        if self == UIFont.self {
            let systemFontMethod = class_getClassMethod(self, #selector(systemFont(ofSize:)))
            let mySystemFontMethod = class_getClassMethod(self, #selector(mySystemFont(ofSize:)))
            method_exchangeImplementations(systemFontMethod!, mySystemFontMethod!)


            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!)
        }
    }
}

and in appDelegate

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool {

    UIFont.overrideInitialize()

    return true
}
Related