30

I'm creating an ios framework with its bundle for packaging ressources (nib, images, fonts) and I'm trying to embed a custom font in the bundle but I'm not able to load it from the framework, is it possible ?

1) I can localize the font file with this: objc NSString *fontPath = [[NSBundle frameworkBundle] pathForResource:@"MyCustomFont" ofType:@"ttf"]; 2) But I can't get it in my fonts lists: objc NSArray * array = [UIFont familyNames]; I included my font name in the bundle's plist with a "Fonts provided by application", without success, tried also in the app info plist, include it in the framework ressource without success.

I can load the nib and images from the bundle (by prefixing with the bundle's name) but not for the font. Any thought ?

EDIT : I saw the following post : Can I embed a custom font in an iPhone application?, but the question is just "Can I embed a custom font in an iPhone application?" not "Can I embed a custom font in an external framework/bundle ?" It also makes references to a dynamic loading which is interesting but it is using private api, which is not usable solution for a framework.

Thanks

Community
  • 1
  • 1
Nicolas Lauquin
  • 1,517
  • 3
  • 14
  • 23
  • 6
    I found a workaround, (still hoping a nicer solution). The user that will include the framework will have to add in its `info.plist` the reference to the font in the bundle : `UIAppFonts key=item0 value=BUNDLENAME.bundle/FONT_FILENAME.ttf` – Nicolas Lauquin Feb 07 '13 at 10:22
  • thanks a lot! Your way helped me out – art-divin Apr 30 '13 at 16:12
  • Framework bundle can be obtained like this: `NSBundle *bundle = [NSBundle bundleForClass:self.class];` – user1132968 May 08 '18 at 07:03

7 Answers7

23

Swift 3:

Firstly, don't access framework bundle from main with appending path components... Instantiate it from its identifier. You can get font URLs like this:

static func fontsURLs() -> [URL] {
    let bundle = Bundle(identifier: "com.company.project.framework")!
    let fileNames = ["Roboto-Bold", "Roboto-Italic", "Roboto-Regular"]
    return fileNames.map({ bundle.url(forResource: $0, withExtension: "ttf")! })
}

And I find it nice to have UIFont extension for registering fonts:

public extension UIFont {
    public static func register(from url: URL) throws {
        guard let fontDataProvider = CGDataProvider(url: url as CFURL) else {
            throw SVError.internal("Could not create font data provider for \(url).")
        }
        let font = CGFont(fontDataProvider)
        var error: Unmanaged<CFError>?
        guard CTFontManagerRegisterGraphicsFont(font, &error) else {
            throw error!.takeUnretainedValue()
        }
    }
}

Now enjoy the registration:

do {
    try fontsURLs().forEach({ try UIFont.register(from: $0) })
} catch {
    print(error)
}
Stanislav Smida
  • 1,565
  • 17
  • 23
21

This is a new method that lets you load fonts dynamically without putting them in your Info.plist: http://www.marco.org/2012/12/21/ios-dynamic-font-loading

David M.
  • 3,667
  • 25
  • 27
17

Here is way I implemented it for my fmk based on the solution provided by "David M." This solution doesn't require to add the reference to the font in the plist.

1) Class that load the font

- (void) loadMyCustomFont{
    NSString *fontPath = [[NSBundle frameworkBundle] pathForResource:@"MyFontFileNameWithoutExtension" ofType:@"ttf"];
    NSData *inData = [NSData dataWithContentsOfFile:fontPath];
    CFErrorRef error;
    CGDataProviderRef provider = CGDataProviderCreateWithCFData((__bridge CFDataRef)inData);
    CGFontRef font = CGFontCreateWithDataProvider(provider);
    if (! CTFontManagerRegisterGraphicsFont(font, &error)) {
        CFStringRef errorDescription = CFErrorCopyDescription(error);
        NSLog(@"Failed to load font: %@", errorDescription);
        CFRelease(errorDescription);
    }
    CFRelease(font);
    CFRelease(provider);
}

2) Category on NSBundle to get access to my bundle

+ (NSBundle *)frameworkBundle {
    static NSBundle* frameworkBundle = nil;
    static dispatch_once_t predicate;
    dispatch_once(&predicate, ^{
        NSString* mainBundlePath = [[NSBundle mainBundle] resourcePath];
        NSString* frameworkBundlePath = [mainBundlePath stringByAppendingPathComponent:@"MyBundleName.bundle"];
        frameworkBundle = [NSBundle bundleWithPath:frameworkBundlePath];
    });
    return frameworkBundle;
}

Note: require to integrate CoreText in your project

Nicolas Lauquin
  • 1,517
  • 3
  • 14
  • 23
  • Not working for me ): I get `Thread 1: EXC_BREAKPOlNT (code=EXC_ARM_BREAKPOINT. subcode=0xdefe)`. Also I tried to remove the to `CFRelease`s in the end and it didn't help.... – Aviel Gross Oct 27 '13 at 10:34
  • Worked for me, did you looked at validated question. It provides a link to the solution I put in place, maybe there is more information ? – Nicolas Lauquin Jun 11 '14 at 12:57
  • Important to note that the resource name string must be the name of the font file, which is not necessarily the font name. – Michael DiStefano Jan 31 '15 at 18:19
4

In swift, I use the code below :

public class func loadMyCustomFont(name:String) -> Bool{
  let fontPath = self.frameworkBundle().pathForResource(name, ofType: "ttf")!
  let inData = NSData(contentsOfFile:fontPath)
  var error: Unmanaged<CFError>?
  let provider = CGDataProviderCreateWithCFData(inData)
  if let font = CGFontCreateWithDataProvider(provider) {
     CTFontManagerRegisterGraphicsFont(font, &error)
     if error != nil {
       print(error) //Or logged it
       return false
     }


      }
       return true
   }

The frameworkBundle method :

class func frameworkBundle() -> NSBundle{
       var bundle = NSBundle()
       var predicate = dispatch_once_t()
       dispatch_once(&predicate) {
          let mainBundlePath = NSBundle.mainBundle().bundlePath
          let frameworkBundlePath = mainBundlePath.stringByAppendingString("/myFramework.framework/")
          bundle = NSBundle(path: frameworkBundlePath)!
       }
       return bundle
 }

Exemple of call : (In my case, i added all fonts in the Fonts folder)

YouClassName.loadMyCustomFont("Fonts/Roboto-Regular")

Your corrections and remarks are welcome !

Ali Abbas
  • 4,247
  • 1
  • 22
  • 40
  • for me it was `NSBundle.mainBundle().privateFrameworksPath`, not `NSBundle.mainBundle().bundlePath` – Tim Apr 18 '16 at 12:51
  • also you should check the return value of a function `CTFontManagerRegisterGraphicsFont` call. Not the `error` parameter – Tim Apr 18 '16 at 13:54
  • You should force unwrap optionals, they are optionals because they can be nil and the app will crash if and when they are nil. Use a `guard` statement to unwrap and exit early or `throw` if nil. – Camsoft Mar 17 '17 at 17:56
1

Updated for Swift 4/5 and changed to throw errors instead of returning a Bool.

enum FontLoadingError: Error {
    case fileNotFound
    case unreadableFontData
}

func loadCustomFont(name: String) throws {
    guard let fontURL = frameworkBundle.url(forResource: name, withExtension: "ttf") else {
        throw FontLoadingError.fileNotFound
    }

    guard
        let provider = CGDataProvider(url: fontURL as CFURL),
        let font = CGFont(provider)
    else {
        throw FontLoadingError.unreadableFontData
    }

    var cfError: Unmanaged<CFError>?
    CTFontManagerRegisterGraphicsFont(font, &cfError)

    if let error = cfError as? Error {
        throw error
    }
}
mhoeller
  • 530
  • 2
  • 9
1

You can use this extension if you have the font in a file/bundle.

public extension UIFont {

    static func register(from url: URL) throws {
        if !FileManager.default.fileExists(atPath: url.path) {
            throw VError.incorrectFont
        }

        var error: Unmanaged<CFError>?

        guard CTFontManagerRegisterFontsForURL(url as CFURL, .process, &error) else {
            throw error!.takeUnretainedValue()
        }
    }
}
SoftDesigner
  • 5,640
  • 3
  • 58
  • 47
0

Swift 3 version of @Ali-ABBAS's answer, also updated to up-wrap options instead of force unwrapping.

fileprivate func loadCustomFont(name:String) -> Bool{

    guard let fontPath = frameworkBundle.path(forResource: name, ofType: "ttf") else {
        return false
    }

    guard let inData = NSData(contentsOfFile:fontPath) else {
        return false
    }


    guard let provider = CGDataProvider(data: inData) else {
        return false
    }

    let font = CGFont(provider)
    var error: Unmanaged<CFError>?
    CTFontManagerRegisterGraphicsFont(font, &error)
    guard error == nil else {
        print(error) //Or logged it
        return false
    }

    return true
}
Camsoft
  • 11,718
  • 19
  • 83
  • 120