47

I added custom font to a framework. I followed all the steps, but it doesn't work.

I am able to set the font in Interface Builder, but when I build the project it doesn't show this font on the simulator/device.

Vojtech Vrbka
  • 5,342
  • 6
  • 44
  • 63
  • 1
    Did you try out the method in my answer? I've been using it in a few apps that use custom fonts bundled with a dynamic framework, without needing to add the fonts to the main project. –  Jun 01 '15 at 11:00
  • 1
    For people looking into the top answers, understand that `CTFontManagerRegisterGraphicsFont` shouldn't be the class to use, they even tell you not to use it in its own doc. Use `CTFontManagerRegisterFontsForURL` based answers at the bottom – itMaxence Oct 04 '21 at 15:45

10 Answers10

47

I'm here a bit late, but I took PetahChristian's solution and created a Swift version in the form of an extension. This is working for me. I've found that when you try to get a font using a font name and a size using the regular way that it always looks in the main bundle for the font file, and there's no method that takes a bundle identifier as a parameter. It would be nice if Apple would make one.

Swift:

public extension UIFont {

    public static func jbs_registerFont(withFilenameString filenameString: String, bundle: Bundle) {

        guard let pathForResourceString = bundle.path(forResource: filenameString, ofType: nil) else {
            print("UIFont+:  Failed to register font - path for resource not found.")
            return
        }

        guard let fontData = NSData(contentsOfFile: pathForResourceString) else {
            print("UIFont+:  Failed to register font - font data could not be loaded.")
            return
        }

        guard let dataProvider = CGDataProvider(data: fontData) else {
            print("UIFont+:  Failed to register font - data provider could not be loaded.")
            return
        }

        guard let font = CGFont(dataProvider) else {
            print("UIFont+:  Failed to register font - font could not be loaded.")
            return
        }

        var errorRef: Unmanaged<CFError>? = nil
        if (CTFontManagerRegisterGraphicsFont(font, &errorRef) == false) {
            print("UIFont+:  Failed to register font - register graphics font failed - this font may have already been registered in the main bundle.")
        }
    }

}

Usage Example:

UIFont.jbs_registerFont(
    withFilenameString: "Boogaloo-Regular.ttf",
    bundle: Bundle(identifier: "com.JBS.JBSFramework")!
)
John Bushnell
  • 1,851
  • 22
  • 29
  • 4
    Instead of using `CTFontManagerRegisterGraphicsFont ` you should use `CTFontManagerRegisterFontsForURL`, and let the loading the contents of the file into memory be handled by the system. – Koen. Aug 19 '19 at 08:38
  • Indeed this answer is outdated. `...RegisterFontsForURL` and `...RegisterFontURLs` are available and SHOULD be used (as specified in the DOC of `...GraphicsFont`). Please upvote the answers at the bottom mentioning it. – itMaxence Aug 02 '21 at 15:25
16

The following solution allows you to load all fonts with a certain extension automatically:

static func registerFonts() {
    let fonts = Bundle(for: 
OrientationMonitor.self).urls(forResourcesWithExtension: "ttf", subdirectory: nil)
    fonts?.forEach({ url in
        CTFontManagerRegisterFontsForURL(url as CFURL, .process, nil)
    })
}

Make sure to replace OrientationMonitor with a class existing in your framework.

Antoine
  • 23,526
  • 11
  • 88
  • 94
  • I went with your approach of explicitly registering everything, as it appears (Xcode 12) that any fonts not in the Main.storyboard weren't getting registered (even though I was doing everything else right...). My minor simplification: `let fonts = Bundle.main.urls(...)` – tiritea Sep 23 '20 at 03:33
  • 1
    You might even want to use the `CTFontManagerRegisterFontURLs(...)` method to remove the `forEach` – itMaxence Aug 02 '21 at 15:31
  • This one works perfectly fine. Thank you so much. – sanket pahuja Aug 29 '21 at 16:34
15

Here's my version of John's answer, showing how to call the function if you have lots of fonts

import Foundation

extension UIFont {

    @nonobjc static var loadAllFontsDO: dispatch_once_t = 0

    class func initialsAvatarFont() -> UIFont {
        loadAllFonts()
        if let retval = UIFont(name: "MyFontName", size: kInitialsAvatarFontSize) {
            return retval;
        } else {
            return UIFont.systemFontOfSize(kInitialsAvatarFontSize)
        }
    }

    class func loadAllFonts() {
        dispatch_once(&loadAllFontsDO) { () -> Void in
            registerFontWithFilenameString("thefontfilename.ttf", bundleIdentifierString: "nameOfResourceBundleAlongsideTheFrameworkBundle")
            // Add more font files here as required
        }
    }

    static func registerFontWithFilenameString(filenameString: String, bundleIdentifierString: String) {
        let frameworkBundle = NSBundle(forClass: AnyClassInYourFramework.self)
        let resourceBundleURL = frameworkBundle.URLForResource(bundleIdentifierString, withExtension: "bundle")
        if let bundle = NSBundle(URL: resourceBundleURL!) {
            let pathForResourceString = bundle.pathForResource(filenameString, ofType: nil)
            let fontData = NSData(contentsOfFile: pathForResourceString!)
            let dataProvider = CGDataProviderCreateWithCFData(fontData)
            let fontRef = CGFontCreateWithDataProvider(dataProvider)
            var errorRef: Unmanaged<CFError>? = nil

            if (CTFontManagerRegisterGraphicsFont(fontRef!, &errorRef) == false) {
                NSLog("Failed to register font - register graphics font failed - this font may have already been registered in the main bundle.")
            }
        }
        else {
            NSLog("Failed to register font - bundle identifier invalid.")
        }
    }
}
xaphod
  • 6,392
  • 2
  • 37
  • 45
  • thanks for this nice solution. One question when passing the bundleIdentifierString, what is meant by "nameOfResourceBundleAlongsideTheFrameworkBundle"? Can you provide an example. Appreciate it! Thanks – Daisy R. Aug 25 '17 at 15:25
  • @DaisyR. It's been a long time since I wrote this, but: this solution is for when you are building a framework and a resource bundle alongside it. In other words you are giving some other developer 2 files, `foo.framework` and `foo.bundle`. The nameOfResourceBundle... refers to `foo.bundle` – xaphod Aug 25 '17 at 19:35
  • Thanks, Unfortunately did this not work in my case and in Swift 3 dispatch_once does not exist, but probably with an extension it's doable. I don't understand the need for this block though for loading fonts. I know this is the solution for when you are building a framework, this is how I got to this post :) – Daisy R. Aug 28 '17 at 20:57
12

Swift 4:

This is maybe an old thread but has updated @xaphod for swift 4 as all static and global variables are lazily initialised using dispatch_once.

extension UIFont {

// load framework font in application
public static let loadAllFonts: () = {
    registerFontWith(filenameString: "SanFranciscoText-Regular.otf", bundleIdentifierString: "Fonts")
    registerFontWith(filenameString: "SanFranciscoText-Medium.otf", bundleIdentifierString: "Fonts")
    registerFontWith(filenameString: "SanFranciscoText-Semibold.otf", bundleIdentifierString: "Fonts")
    registerFontWith(filenameString: "SanFranciscoText-Bold.otf", bundleIdentifierString: "Fonts")
    registerFontWith(filenameString: "SanFranciscoText-LightItalic.otf", bundleIdentifierString: "Fonts")
}()

//MARK: - Make custom font bundle register to framework
static func registerFontWith(filenameString: String, bundleIdentifierString: String) {
    let frameworkBundle = Bundle(for: MSAlertController.self)
    let resourceBundleURL = frameworkBundle.url(forResource: bundleIdentifierString, withExtension: "bundle")
    if let url = resourceBundleURL, let bundle = Bundle(url: url) {
        let pathForResourceString = bundle.path(forResource: filenameString, ofType: nil)
        if let fontData = NSData(contentsOfFile: pathForResourceString!), let dataProvider = CGDataProvider.init(data: fontData) {
            let fontRef = CGFont.init(dataProvider)
            var errorRef: Unmanaged<CFError>? = nil
            if (CTFontManagerRegisterGraphicsFont(fontRef!, &errorRef) == false) {
                print("Failed to register font - register graphics font failed - this font may have already been registered in the main bundle.")
            }
        }
    }
    else {
        print("Failed to register font - bundle identifier invalid.")
    }
}
}

Then you can call UIFont.loadAllfont inside the appDelegate

William Hu
  • 15,423
  • 11
  • 100
  • 121
manismku
  • 2,160
  • 14
  • 24
10

I made a mix of different answers in Swift 4.2 so props to the guys who came up with it!

import UIKit
import Foundation

extension UIFont {

    private class MyDummyClass {}

    static func loadFontWith(name: String) {
        let frameworkBundle = Bundle(for: MyDummyClass.self)
        let pathForResourceString = frameworkBundle.path(forResource: name, ofType: "ttf")
        let fontData = NSData(contentsOfFile: pathForResourceString!)
        let dataProvider = CGDataProvider(data: fontData!)
        let fontRef = CGFont(dataProvider!)
        var errorRef: Unmanaged<CFError>? = nil

        if (CTFontManagerRegisterGraphicsFont(fontRef!, &errorRef) == false) {
            NSLog("Failed to register font - register graphics font failed - this font may have already been registered in the main bundle.")
        }
    }

    public static let loadMyFonts: () = {
        loadFontWith(name: "Exo-Black")
        loadFontWith(name: "Exo-Bold")
        loadFontWith(name: "Exo-Regular")    
    }()
}

And then call in Appdelegate

UIFont.loadMyFonts
makle
  • 1,237
  • 1
  • 15
  • 21
  • From the documentation for `CTFontManagerRegisterGraphicsFont(_:_:)` > Fonts that are backed by files should be registered using `CTFontManagerRegisterFontsForURL(_:_:_:).` Since this example is loading a font from a file, I assume it's not the intended way to to use this API. – B Roy Dawson May 12 '23 at 16:33
8

You can load and use bundled custom fonts from your dynamic framework by implementing the +load method in your framework.

In the load method, you locate the fonts in the bundle, then register them. This makes them available to the app, without having to specify them in the main project.

+ (void)load
{
    static dispatch_once_t onceToken;
     dispatch_once(&onceToken, ^{
        // Dynamically load bundled custom fonts

        [self bible_loadFontWithName:kBIBLECustomFontBoldName];
        [self bible_loadFontWithName:kBIBLECustomFontBoldItalicName];
        [self bible_loadFontWithName:kBIBLECustomFontItalicName];
        [self bible_loadFontWithName:kBIBLECustomFontRegularName];
    });
}

+ (void)bible_loadFontWithName:(NSString *)fontName
{
     NSString *fontPath = [[NSBundle bundleForClass:[BIBLE class]] pathForResource:fontName ofType:@"otf"];
     NSData *fontData = [NSData dataWithContentsOfFile:fontPath];

     CGDataProviderRef provider = CGDataProviderCreateWithCFData((CFDataRef)fontData);

     if (provider)
     {
        CGFontRef font = CGFontCreateWithDataProvider(provider);

         if (font)
         {
             CFErrorRef error = NULL;
             if (CTFontManagerRegisterGraphicsFont(font, &error) == NO)
             {
                 CFStringRef errorDescription = CFErrorCopyDescription(error);
                 NSLog(@"Failed to load font: %@", errorDescription);
                 CFRelease(errorDescription);
             }

             CFRelease(font);
        }

        CFRelease(provider);
    }
}
8

I've created an extension on UIFont that will register all fonts (of a given type) that are found in a bundle. Note that since this registers the fonts, there is no need to include the 'Fonts provided by application' in the framework's Info.plist.

extension UIFont {

    private static var fontsRegistered: Bool = false

    static func registerFontsIfNeeded() {
        guard
            !fontsRegistered,
            let fontURLs = someBundle?.urls(forResourcesWithExtension: "otf", subdirectory: nil)
        else { return }

        fontURLs.forEach({ CTFontManagerRegisterFontsForURL($0 as CFURL, .process, nil) })
        fontsRegistered = true
    }
}

then just make sure to call registerFontsIfNeeded() before you attempt to create an instance of your custom font. i.e

...
registerFontsIfNeeded()

let myCustomFont = UIFont(name: "My-Custom-Font-Name", size: 20)
...
Simo
  • 2,172
  • 3
  • 19
  • 23
7

Found a really easy and readable way to register font, which is not mentioned here:

func registerFont(with fontName: String) {
    guard let url = Bundle(for: BundleToken.self).url(forResource: fontName, withExtension: nil),
        CTFontManagerRegisterFontsForURL(url as CFURL, .process, nil) else {
            fatalError("Failed to register font: \(font.fileName)")
    }
}

private final class BundleToken {}
Alex Shubin
  • 3,549
  • 1
  • 27
  • 32
  • Thank you! I found this API works with `UIFontPickerViewController` whereas fonts registered with `CTFontManagerRegisterGraphicsFont ` do not appear in the list for some reason. – Jordan H Aug 12 '19 at 05:42
2

Was able to do this with Swift 4, given that you can include resources directly in a framework bundle now:

Typography.swift (in my framework)

import Foundation

private class MyDummyClass {}

func loadFontWith(name: String) {
  let frameworkBundle = Bundle(for: MyDummyClass.self)
  let pathForResourceString = frameworkBundle.path(forResource: name, ofType: "otf")
  let fontData = NSData(contentsOfFile: pathForResourceString!)
  let dataProvider = CGDataProvider(data: fontData!)
  let fontRef = CGFont(dataProvider!)
  var errorRef: Unmanaged<CFError>? = nil

  if (CTFontManagerRegisterGraphicsFont(fontRef!, &errorRef) == false) {
    NSLog("Failed to register font - register graphics font failed - this font may have already been registered in the main bundle.")
  }
}

public func loadMyFonts() {
  loadFontWith(name: "ComicSansPro-Regular")
  loadFontWith(name: "ComicSansPro-Medium")
  loadFontWith(name: "ComicSansPro-Bold")
  loadFontWith(name: "ComicSansPro-ExtraBold")
}

I ended up requiring the loadMyFonts method to be called in the application's that use this framework's didFinishLaunchingWithOptions method in AppDelegate.swift.

Kathryn Gonzalez
  • 334
  • 1
  • 4
  • 8
1

I thought I'd share my answer as well. My project is set up like so:

  • Main iOS App (Swift)

    • Dynamic Framework (Obj-C)

      • Fonts.bundle (a bundle with all the fonts inside)

      • UIFont categories

      • NSBundle categories

      • Other framework classes

    • App Classes (ViewControllers, Models, CoreData, etc...)

My goal was to be able to have the main app call a single method on the dynamic framework to load fonts without the need for altering the Info.plist or adding the font files/bundle to the main target.

@import CoreText;

@implementation NSBundle (Fonts)

+ (NSBundle *)fontsBundle {
    // The only way I could find to do this is to hard code the sub-path. Using pathForResource doesn't seem to find Fonts.bundle, nor its contents\
    // This way the host app doesn't need to copy Fonts.bundle
    NSString *path = [[[NSBundle mainBundle] bundlePath] stringByAppendingString:@"/Frameworks/<YourFrameworkName>.framework/Fonts.bundle"];
    NSBundle *bundle = [NSBundle bundleWithPath:path];
    if (bundle == nil) {
        NSLog(@"Warning: Fonts.bundle could not be loaded. Have you included it in your target?");
    }
    return bundle;
}

- (BOOL)loadFonts {

    NSArray<NSString *> *names = @[
        @"GothamRnd-Bold",
        @"GothamRnd-BoldItal",
        @"GothamRnd-Book",
        @"GothamRnd-BookItal",
        @"GothamRnd-Light",
        @"GothamRnd-LightItal",
        @"GothamRnd-MedItal",
        @"GothamRnd-Medium",
    ];

    __block NSInteger failCounter = 0;
    [names enumerateObjectsUsingBlock:^(id _Nonnull name, NSUInteger idx, BOOL *_Nonnull stop) {
        NSString *fontPath = [self pathForResource:name ofType:@"otf"];
        NSData *inData = [NSData dataWithContentsOfFile:fontPath];
        CFErrorRef error;
        CGDataProviderRef provider = CGDataProviderCreateWithCFData((__bridge CFDataRef)inData);
        CGFontRef font = CGFontCreateWithDataProvider(provider);

        if (!CTFontManagerRegisterGraphicsFont(font, &error)) {
            if (error) {
                NSLog(@"Failed to load font at path: %@", fontPath);
                failCounter++;
            }
            CFStringRef errorDescription = CFErrorCopyDescription(error);
            NSLog(@"Failed to load font: %@", errorDescription);
            CFRelease(errorDescription);
        }
        CFRelease(font);
        CFRelease(provider);

    }];

    return failCounter == 0;
}

@end

The only bummer in this code is you have to hard code the path to the Fonts.bundle. I couldn't get any combination of NSBundle methods to locate the Fonts.bundle file automatically. For instance no methods like this would return a path:

NSString *pathToBundle = [[NSBundle mainBundle] pathForResource:@"Fonts" ofType:@"bundle"];
NSString *pathToFont = [[NSBundle mainBundle] pathForResource:@"MyFont" ofType:@"ttf"];

Aside from the hard coding (which will never change), this is working for me well enough though. I can now skin all of my client apps easily.

VaporwareWolf
  • 10,143
  • 10
  • 54
  • 80