Xcode: использование пользовательских шрифтов внутри динамической структуры

Я добавил пользовательский шрифт в фреймворк. Я выполнил все шаги, но это не сработало.

Я могу установить шрифт в Interface Builder, но когда я создаю проект, он не отображает этот шрифт на симуляторе/устройстве.

Ответ 1

Я немного опоздал, но я выбрал решение PetahChristian и создал версию Swift в виде расширения. Это работает для меня. Я обнаружил, что когда вы пытаетесь получить шрифт, используя имя шрифта и размер, используя обычный способ, которым он всегда выглядит в главном пакете для файла шрифта, и не существует метода, который принимает идентификатор пакета в качестве параметра. Было бы хорошо, если бы Apple сделала один.

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

Ответ 2

Здесь моя версия Джона отвечает, показывая, как вызвать функцию, если у вас много шрифтов

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

Ответ 3

Вы можете загружать и использовать встраиваемые пользовательские шрифты из своей динамической структуры, внедряя метод + 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);
    }
}

Ответ 4

Свифт 4:

Возможно, это старый поток, но он обновил @xaphod для swift 4, поскольку все статические и глобальные переменные лениво инициализируются с использованием 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.")
    }
}
}

Затем вы можете вызвать UIFont.loadAllfont внутри appDelegate

Ответ 5

Я думал, что тоже поделюсь своим ответом. Мой проект настроен так:

  • Основное приложение iOS (Swift)

    • Динамическая структура (Obj-C)

      • Fonts.bundle(набор со всеми шрифтами внутри)

      • Категории UIFont

      • Категории NSBundle

      • Другие классы инфраструктуры

    • Классы приложений (ViewControllers, Models, CoreData и т.д.)

Моя цель состояла в том, чтобы иметь возможность вызвать вызов основного приложения одним методом в динамической структуре для загрузки шрифтов без необходимости изменения Info.plist или добавления файлов/пакета шрифтов к основной цели.

@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. Например, никакие методы вроде этого не возвращают путь:

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

Помимо жесткого кодирования (которое никогда не изменится), это работает для меня достаточно хорошо, хотя. Теперь я могу легко скрыть все мои клиентские приложения.

Ответ 6

Был в состоянии сделать это с 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")
}

Я закончил тем, что требовал, loadMyFonts метод loadMyFonts был вызван в приложении, которое использует этот каркас, didFinishLaunchingWithOptions метод AppDelegate.swift в AppDelegate.swift.

Ответ 7

Нашел действительно простой и читаемый способ регистрации шрифта, о котором здесь не упоминается:

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

Ответ 8

Я сделал смесь разных ответов в 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