I want to use a custom font within a Cocoapod, but I can\'t find anything on using a custom font within a static library. As there is no info.plist file, there is no where to te
If I understand correctly, you are trying to provide a font with your Cocoapod, and you intent the iOS apps which include the pod to be able to use your custom font.
This post_install
hook seems to work:
Pod::Spec.new do |s|
# ...
s.resources = "Resources/*.otf"
# ...
s.post_install do |library_representation|
require 'rexml/document'
library = library_representation.library
proj_path = library.user_project_path
proj = Xcodeproj::Project.new(proj_path)
target = proj.targets.first # good guess for simple projects
info_plists = target.build_configurations.inject([]) do |memo, item|
memo << item.build_settings['INFOPLIST_FILE']
end.uniq
info_plists = info_plists.map { |plist| File.join(File.dirname(proj_path), plist) }
resources = library.file_accessors.collect(&:resources).flatten
fonts = resources.find_all { |file| File.extname(file) == '.otf' || File.extname(file) == '.ttf' }
fonts = fonts.map { |f| File.basename(f) }
info_plists.each do |plist|
doc = REXML::Document.new(File.open(plist))
main_dict = doc.elements["plist"].elements["dict"]
app_fonts = main_dict.get_elements("key[text()='UIAppFonts']").first
if app_fonts.nil?
elem = REXML::Element.new 'key'
elem.text = 'UIAppFonts'
main_dict.add_element(elem)
font_array = REXML::Element.new 'array'
main_dict.add_element(font_array)
else
font_array = app_fonts.next_element
end
fonts.each do |font|
if font_array.get_elements("string[text()='#{font}']").empty?
font_elem = REXML::Element.new 'string'
font_elem.text = font
font_array.add_element(font_elem)
end
end
doc.write(File.open(plist, 'wb'))
end
end
The hook finds the user project, and in the first target (you probably can complete this solution by asking CocoaPods to give you the real target) it looks for its Info.plist
file(s) (normally there is only one). Finally it looks for the UIAppFonts
key of the file, creates it if not found, and fill the array with the font names if they are not already there.
Well, idk if it can be an answer, but you can also look to cocoapod that has needed font, like this: https://github.com/parakeety/GoogleFontsiOS
Library contains many fonts, i needed Chivo, so i added pod 'GoogleFontsiOS/Chivo' and used it instead of writing font-loading code by myself.
There is a way of using a custom font without adding anything to the plist file.
NSBundle *bundle = [NSBundle bundleForClass:[self class]];
NSURL *fontURL = [bundle URLForResource:<#fontName#> withExtension:@"otf"/*or TTF*/];
NSData *inData = [NSData dataWithContentsOfURL:fontURL];
CFErrorRef error;
CGDataProviderRef provider = CGDataProviderCreateWithCFData((CFDataRef)inData);
CGFontRef font = CGFontCreateWithDataProvider(provider);
if (!CTFontManagerRegisterGraphicsFont(font, &error)) {
CFStringRef errorDescription = CFErrorCopyDescription(error);
NSLog(@"Failed to load font: %@", errorDescription);
CFRelease(errorDescription);
}
CFSafeRelease(font);
CFSafeRelease(provider);
You also need the CFSafeRelease
function for this to work.
void CFSafeRelease(CFTypeRef cf) {
if (cf != NULL) {
CFRelease(cf);
}
}
Source: Loading iOS fonts dynamically.
Swift equivalent:
extension UIFont {
static func registerFont(bundle: Bundle, fontName: String, fontExtension: String) -> Bool {
guard let fontURL = bundle.url(forResource: fontName, withExtension: fontExtension) else {
fatalError("Couldn't find font \(fontName)")
}
guard let fontDataProvider = CGDataProvider(url: fontURL as CFURL) else {
fatalError("Couldn't load data from the font \(fontName)")
}
guard let font = CGFont(fontDataProvider) else {
fatalError("Couldn't create font from data")
}
var error: Unmanaged<CFError>?
let success = CTFontManagerRegisterGraphicsFont(font, &error)
guard success else {
print("Error registering font: maybe it was already registered.")
return false
}
return true
}
}
For those of you finding this in 2018+, I got custom fonts to work with interface builder support (XCode 9) with these two steps:
Add your fonts to the resource bundle of your framework
s.resources = "PodName/**/*.{ttf}"
Load fonts at runtime using Adam's answer above
#import <CoreText/CoreText.h>
void CFSafeRelease(CFTypeRef cf) { // redefine this
if (cf != NULL) {
CFRelease(cf);
}
}
+ (void) loadFonts {
NSBundle *frameworkBundle = [NSBundle bundleForClass:self.classForCoder];
NSURL *bundleURL = [[frameworkBundle resourceURL] URLByAppendingPathComponent:@"PodName.bundle"];
NSBundle *bundle = [NSBundle bundleWithURL:bundleURL];
NSURL *fontURL = [bundle URLForResource:@"HindMadurai-SemiBold" withExtension:@"ttf"];
NSData *inData = [NSData dataWithContentsOfURL:fontURL];
CFErrorRef error;
CGDataProviderRef provider = CGDataProviderCreateWithCFData((CFDataRef)inData);
CGFontRef font = CGFontCreateWithDataProvider(provider);
if (!CTFontManagerRegisterGraphicsFont(font, &error)) {
CFStringRef errorDescription = CFErrorCopyDescription(error);
NSLog(@"Failed to load font: %@", errorDescription);
CFRelease(errorDescription);
}
CFSafeRelease(font);
CFSafeRelease(provider);
}
Swift 5 implementation
I was able to solve this by creating the below class in my Cocoapod, then just calling CustomFonts.loadAll()
from my main app's AppDelegate.swift
. After that I can just use a font like this in my app:
let myFont = CustomFonts.Style.regular.font
Note that the Style
enum is not necessary, just a convenient way to separate concerns. You could also just call:
let myFont = UIFont(name: "SourceSansPro-SemiBold", size: 14)
import CoreText
public class CustomFonts: NSObject {
public enum Style: CaseIterable {
case mono
case regular
case semibold
public var value: String {
switch self {
case .mono: return "SourceCodePro-Medium"
case .regular: return "SourceSansPro-Regular"
case .semibold: return "SourceSansPro-SemiBold"
}
}
public var font: UIFont {
return UIFont(name: self.value, size: 14) ?? UIFont.init()
}
}
// Lazy var instead of method so it's only ever called once per app session.
public static var loadFonts: () -> Void = {
let fontNames = Style.allCases.map { $0.value }
for fontName in fontNames {
loadFont(withName: fontName)
}
return {}
}()
private static func loadFont(withName fontName: String) {
guard
let bundleURL = Bundle(for: self).url(forResource: "[CococpodName]", withExtension: "bundle"),
let bundle = Bundle(url: bundleURL),
let fontURL = bundle.url(forResource: fontName, withExtension: "ttf"),
let fontData = try? Data(contentsOf: fontURL) as CFData,
let provider = CGDataProvider(data: fontData),
let font = CGFont(provider) else {
return
}
CTFontManagerRegisterGraphicsFont(font, nil)
}
}