6

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?

Charles Rostaing
  • 548
  • 5
  • 17
  • Probably `self` (UILabel) is nil at this point. Try to call it at later stage. – jonaszmclaren Sep 25 '17 at 11:56
  • For some reason some answers as well as my responses to those answers were deleted (oO ?) : The person suggested replacing self with UILabel.appearance() : this didn't resolve the issue. – Charles Rostaing Sep 25 '17 at 13:09
  • Based on Sandy Chapman's answer : https://stackoverflow.com/a/28440649/5778002 Fully working on Swift 3. On Swift 4 : self.font returns nil – Charles Rostaing Sep 25 '17 at 14:37
  • I am facing exactly the same issue. I tried to do it in viewDidAppear but still nil value for self.font.pointSize – Nabeel Oct 09 '17 at 14:44
  • Gave up. I changed all the fonts in the storyboard directly via ctrl-F ... – Charles Rostaing Oct 10 '17 at 15:40
  • Having same issue now, dont really like subclass or manually change...but this doesnt work anymore, weird – Tj3n Oct 12 '17 at 04:42

3 Answers3

7

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 {
Litle Dev
  • 474
  • 1
  • 5
  • 18
6

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
Tj3n
  • 9,837
  • 2
  • 24
  • 35
  • Thanks!! I can't really comprehend why, but it works. Do you have a theory why this works? @Tj3n – barley Sep 08 '18 at 17:28
  • 1
    @barley It seems like the `UIAppearance` only able to read the function (maybe via KVC) if it's exposed to objc runtime, the keyword did that. Maybe Apple haven't added support for this feature in Swift yet. – Tj3n Sep 10 '18 at 02:50
  • Hmm. Yeah that kind of convinces me, but kind of not... `UIApperance` can certainly read `substituteFontName` because it runs the `set` method inside it. But it somehow cannot access `UILabel.font` attribute if `substituteFontName` is not exposed as Obj-C interface.. – barley Sep 10 '18 at 18:50
2

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
}
Bassant Ashraf
  • 1,531
  • 2
  • 16
  • 23