Xcode:在动态框架中使用自定义字体

Xcode: Using custom fonts inside Dynamic framework

我在框架中添加了自定义字体。我按照所有步骤操作,但它不起作用。

我可以在 Interface Builder 中设置字体,但是当我构建项目时它没有在 simulator/device 上显示该字体。

您可以通过在框架中实现 +load 方法从动态框架加载和使用捆绑的自定义字体。

load 方法中,您在包中找到字体,然后注册它们。这使它们可供应用程序使用,而无需在主项目中指定它们。

+ (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);
    }
}

我来晚了,但我采用了 PetahChristian 的解决方案,并以扩展的形式创建了一个 Swift 版本。这对我有用。我发现,当您尝试使用字体名称和大小使用常规方式获取字体时,它总是在字体文件的主包中查找,并且没有方法将包标识符作为参数。如果苹果能做一个就好了。

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.")
        }
    }

}

用法示例:

UIFont.jbs_registerFont(
    withFilenameString: "Boogaloo-Regular.ttf",
    bundle: Bundle(identifier: "com.JBS.JBSFramework")!
)

这是我对 John 回答的版本,展示了如果您有很多字体,如何调用该函数

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.")
        }
    }
}

我想我也会分享我的答案。我的项目是这样设置的:

  • 主要 iOS 应用程序 (Swift)

    • 动态框架 (Obj-C)

      • Fonts.bundle(包含所有字体的包)

      • UIFont 类别

      • NSBundle 类别

      • 其他框架类

    • App 类(ViewControllers、模型、CoreData 等...)

我的目标是能够让主应用程序调用动态框架上的单个方法来加载字体,而无需更改 Info.plist 或将字体 files/bundle 添加到主应用程序目标。

@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

此代码中唯一的麻烦是您必须对 Fonts.bundle 的路径进行硬编码。我无法获得 NSBundle 方法的任何组合来自动定位 Fonts.bundle 文件。例如,没有像这样的方法会 return 路径:

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

除了硬编码(永远不会改变)之外,这对我来说已经足够好了。我现在可以轻松地对所有客户端应用程序进行换肤。

能够使用 Swift 4 做到这一点,因为您现在可以将资源直接包含在框架包中:

Typography.swift(在我的框架中)

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")
}

我最终要求在 AppDelegate.swift 中使用此框架的 didFinishLaunchingWithOptions 方法的应用程序中调用 loadMyFonts 方法。

Swift 4:

这可能是一个旧线程,但已为 swift 4 更新了@xaphod,因为所有静态和全局变量都使用 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.")
    }
}
}

然后你可以在appDelegate

里面调用UIFont.loadAllfont

找到了一个非常简单易读的注册字体的方法,这里就不提了:

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 {}

我在 Swift 4.2 中混合了不同的答案,所以支持提出它的人!

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")    
    }()
}

然后调用Appdelegate

UIFont.loadMyFonts

以下解决方案允许您自动加载具有特定扩展名的所有字体:

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

确保将 OrientationMonitor 替换为框架中现有的 class。

我在 UIFont 上创建了一个扩展,它将注册在一个包中找到的所有字体(给定类型)。请注意,由于这会注册字体,因此无需在框架的 Info.plist.

中包含 'Fonts provided by application'
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([=10=] as CFURL, .process, nil) })
        fontsRegistered = true
    }
}

然后确保在尝试创建自定义字体实例之前调用 registerFontsIfNeeded()。即

...
registerFontsIfNeeded()

let myCustomFont = UIFont(name: "My-Custom-Font-Name", size: 20)
...