Xcode: Using custom fonts inside Dynamic framework

前端 未结 9 1548
再見小時候
再見小時候 2020-12-04 17:52

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 d

相关标签:
9条回答
  • 2020-12-04 18:37

    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.

    0 讨论(0)
  • 2020-12-04 18:41

    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 {}
    
    0 讨论(0)
  • 2020-12-04 18:43

    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")!
    )
    
    0 讨论(0)
提交回复
热议问题