Soheil Novinfard
Soheil Novinfard

Reputation: 1444

Load fonts from a Swift Package

I wonder if there is a way to load fonts from a Swift Package at the moment? The font files are there in the package but the compiled program can't locate them.

Upvotes: 6

Views: 3836

Answers (2)

Mojtaba Hosseini
Mojtaba Hosseini

Reputation: 119646

✅ It is supported now

From Swift 5.3, you can add any resources to the target, including images, assets, fonts, zip, etc. Using the directory name will include all subfiles of the directory:

    .target(
        name: "ABUIKit",
        dependencies: [],
        resources: [.process("Resources") // <- this will add Resource directory to the target
        ]
    ),

Note that you should put the Resources folder under sources/packageName to make it recognizable.

⚠️ Also, Don't forget to register them!

You need to register fonts to make it work. So you can use frameworks like FontBlaster.

NOTE: You can always write your own code, but I prefer to use an existing framework and contribute my extra needs to them

So you call this somewhere early in the module's code (like some init method):

FontBlaster.blast(bundle: .module)

Then you can use the font inside or even outside of the module!


🛑 There is no need to name the font manually!

It will register all included fonts automatically. You can double-check the loaded fonts right after calling the blast method:

FontBlaster.loadedFonts.forEach { print("🔠", $0) }

Upvotes: 7

DungeonDev
DungeonDev

Reputation: 1226

Mojtaba Hosseini's answer is correct but in order to use the fonts of your package you need to register them also. You could do it with a little supporting function in a struct... I use this in my projects:

public struct Appearance {

    /// Configures all the UI of the package
    public static  func configurePackageUI() {
        loadPackageFonts()
    }

    static func loadPackageFonts() {
    
        // All the filenames of your custom fonts here
        let fontNames = ["Latinotype - Texta-Black.otf",
                         "Latinotype - Texta-BlackIt.otf",
                         "Latinotype - Texta-Bold.otf",
                         "Latinotype - Texta-BoldIt.otf",
                         "Latinotype - Texta-Book.otf",
                         "Latinotype - Texta-BookIt.otf",
                         "Latinotype - Texta-Heavy.otf",
                         "Latinotype - Texta-HeavyIt.otf",
                         "Latinotype - Texta-It.otf",
                         "Latinotype - Texta-Light.otf",
                         "Latinotype - Texta-LightIt.otf",
                         "Latinotype - Texta-Medium.otf",
                         "Latinotype - Texta-MediumIt.otf",
                         "Latinotype - Texta-Regular.otf",
                         "Latinotype - Texta-Thin.otf",
                         "Latinotype - Texta-ThintIt.otf",
        ]
    
        fontNames.forEach{registerFont(fileName: $0)}
    }

    static func registerFont(fileName: String) {
        guard let gFontRef = getFont(named: fileName) else {
            print("*** ERROR: ***")
            return
    }
    
    var errorRef: Unmanaged<CFError>? = nil
        if !CTFontManagerRegisterGraphicsFont(gFontRef, &errorRef) {
            print("*** ERROR: \(errorRef.debugDescription) ***")
        }
    }

    static func getFont(named fileName: String) -> CGFont? {
        let url = Bundle.module.url(forResource: fileName, withExtension: nil)
        guard let gUrl = url,
              let gFontData = NSData(contentsOf: gUrl),
              let gDataProvider = CGDataProvider(data: gFontData),
              let gFontRef = CGFont(gDataProvider) else {
                print("*** ERROR: ***")
                return nil
    }
    
        return gFontRef
    }
}

Just remember to add the font filename in the array.

EDIT: I've updated my answer.

Upvotes: 9

Related Questions