I managed to import custom fonts using SPM, using this SO answer to help https://stackoverflow.com/a/36871032/5508175
Here is what I did. Create your package and add your fonts. Here is my Package.swift
// swift-tools-version:5.3
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "MyFonts",
products: [
.library(
name: "MyFonts",
targets: ["MyFonts"]),
],
dependencies: [
],
targets: [
.target(
name: "MyFonts",
dependencies: [],
resources: [.process("Fonts")]),
.testTarget(
name: "MyFontsTests",
dependencies: ["MyFonts"]),
]
)
Here is my folder structure. I have all of my fonts contained in a folder called Fonts.

Inside MyFonts.swift
I do the following:
import Foundation // This is important remember to import Foundation
public let fontBundle = Bundle.module
This allows me access to the Bundle outside of the package.
Next I added the package to my project. It is a SwiftUI project with an AppDelegate.
- import MyFonts
- In
didFinishLaunchingWithOptions
check to see if the font files are available (optional)
- Add the fonts using the extension to UIFont.
- Print out the fonts to check that they are installed (optional)
So here is my AppDelegate:
import UIKit
import MyFonts
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// This prints out the files that are stored in the MyFont bundle
// Just doing this to check that the fonts are actually in the bundle
if let files = try? FileManager.default.contentsOfDirectory(atPath: fontBundle.bundlePath ){
for file in files {
print(file)
}
}
// This registers the fonts
_ = UIFont.registerFont(bundle: fontBundle, fontName: "FiraCode-Medium", fontExtension: "ttf")
_ = UIFont.registerFont(bundle: fontBundle, fontName: "FiraCode-Bold", fontExtension: "ttf")
_ = UIFont.registerFont(bundle: fontBundle, fontName: "FiraCode-Light", fontExtension: "ttf")
_ = UIFont.registerFont(bundle: fontBundle, fontName: "FiraCode-Regular", fontExtension: "ttf")
_ = UIFont.registerFont(bundle: fontBundle, fontName: "FiraCode-Retina", fontExtension: "ttf")
// This prints out all the fonts available you should notice that your custom font appears in this list
for family in UIFont.familyNames.sorted() {
let names = UIFont.fontNames(forFamilyName: family)
print("Family: \(family) Font names: \(names)")
}
return true
}
// MARK: UISceneSession Lifecycle
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}
func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {}
}
// This extension is taken from this SO answer https://stackoverflow.com/a/36871032/5508175
extension UIFont {
static func registerFont(bundle: Bundle, fontName: String, fontExtension: String) -> Bool {
guard let fontURL = bundle.url(forResource: fontName, withExtension: fontExtension) else {
fatalError("Couldn't find font \(fontName)")
}
guard let fontDataProvider = CGDataProvider(url: fontURL as CFURL) else {
fatalError("Couldn't load data from the font \(fontName)")
}
guard let font = CGFont(fontDataProvider) else {
fatalError("Couldn't create font from data")
}
var error: Unmanaged<CFError>?
let success = CTFontManagerRegisterGraphicsFont(font, &error)
guard success else {
print("Error registering font: maybe it was already registered.")
return false
}
return true
}
}
Then in you ContentView
you can do something like this:
import SwiftUI
struct ContentView: View {
var body: some View {
VStack(spacing: 20) {
Text("Hello San Francisco")
Text("Hello FiraCode Medium").font(Font.custom("FiraCode-Medium", size: 16))
Text("Hello FiraCode Bold").font(Font.custom("FiraCode-Bold", size: 16))
Text("Hello FiraCode Light").font(Font.custom("FiraCode-Light", size: 16))
Text("Hello FiraCode Regular").font(Font.custom("FiraCode-Regular", size: 16))
Text("Hello FiraCode Retina").font(Font.custom("FiraCode-Retina", size: 16))
}
}
}
Which gives the following result:

Caveats
I haven't tried this in a fully SwiftUI app, but you can follow the tutorial shown here on how to add an AppDelegate if you don't have one.
Obviously the printing of the files in the fontBundle
and the fonts that are installed are optional. They are just useful for debugging and for making sure that you have the correct font name The filename can differ quite considerably from the font name that you have to use to display the font. See my SO post about adding custom fonts:
Update
I wondered if it was possible to create a function that was contained in the package and calling that would load the fonts. Apparently it is.
I updated to MyFonts.swift
to the following:
import Foundation
import UIKit
public func registerFonts() {
_ = UIFont.registerFont(bundle: .module, fontName: "FiraCode-Medium", fontExtension: "ttf")
_ = UIFont.registerFont(bundle: .module, fontName: "FiraCode-Bold", fontExtension: "ttf")
_ = UIFont.registerFont(bundle: .module, fontName: "FiraCode-Light", fontExtension: "ttf")
_ = UIFont.registerFont(bundle: .module, fontName: "FiraCode-Regular", fontExtension: "ttf")
_ = UIFont.registerFont(bundle: .module, fontName: "FiraCode-Retina", fontExtension: "ttf")
}
extension UIFont {
static func registerFont(bundle: Bundle, fontName: String, fontExtension: String) -> Bool {
guard let fontURL = bundle.url(forResource: fontName, withExtension: fontExtension) else {
fatalError("Couldn't find font \(fontName)")
}
guard let fontDataProvider = CGDataProvider(url: fontURL as CFURL) else {
fatalError("Couldn't load data from the font \(fontName)")
}
guard let font = CGFont(fontDataProvider) else {
fatalError("Couldn't create font from data")
}
var error: Unmanaged<CFError>?
let success = CTFontManagerRegisterGraphicsFont(font, &error)
guard success else {
print("Error registering font: maybe it was already registered.")
return false
}
return true
}
}
This meant that I could remove the extension from the AppDelegate and I don't have to register each font in the AppDelegate like I did before I just call registerFonts()
So my didFinishLaunchingWithOptions
now looks like this:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// This registers the fonts
registerFonts()
return true
}
Remember that you still have to import your package.