Skip to content

Instantly share code, notes, and snippets.

@shaps80
Last active March 19, 2024 14:54
Show Gist options
  • Star 17 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save shaps80/046df819d2a68c68ae6011262c6e0fc1 to your computer and use it in GitHub Desktop.
Save shaps80/046df819d2a68c68ae6011262c6e0fc1 to your computer and use it in GitHub Desktop.
Better font loading in iOS with Swift
extension UIFont {
// The `rawValue` MUST match the filename (without extension)
public enum Graphik: String, FontCacheDescriptor {
case regular = "GraphikAltWeb-Regular"
case medium = "GraphikAltWeb-Medium"
case regularItalic = "GraphikAltWeb-RegularItalic"
case mediumItalic = "GraphikAltWeb-MediumItalic"
}
/// Makes a new font with the specified variant & size
public convenience init(graphik: Graphik, size: CGFloat) {
self.init(descriptor: graphik, size: size)
}
}
label.font = UIFont(graphik: .regular, size: UIFont.labelFontSize)
import UIKit
import CoreText
public protocol FontCacheDescriptor: Codable {
var fontName: String { get }
var fileName: String { get }
var fileExtension: String { get }
}
public extension FontCacheDescriptor {
public var fileExtension: String {
return "ttf"
}
}
public extension FontCacheDescriptor where Self: RawRepresentable, Self.RawValue == String {
public var fontName: String {
return rawValue
}
public var fileName: String {
return rawValue
}
}
extension UIFont {
public convenience init(descriptor: FontCacheDescriptor, size: CGFloat) {
FontCache.cacheIfNeeded(named: descriptor.fileName, fileExtension: descriptor.fileExtension)
let size = UIFontMetrics.default.scaledValue(for: size)
let descriptor = UIFontDescriptor(name: descriptor.fontName, size: size)
self.init(descriptor: descriptor, size: 0)
}
}
public class FontCache {
/// iOS already caches the fonts themselves, but doesn't provide a way to query 'already loaded fonts'.
/// So this cache is just to keep track so we don't load the font more than once.
static private var cache = [String]()
/// Loads the font and gets iOS to cache it.
///
/// - Parameter name: The name of the font to cache
public static func cacheIfNeeded(named name: String, fileExtension: String) {
guard !cache.contains(name) else { return }
guard let url = Bundle.main.url(forResource: name, withExtension: fileExtension) else {
fatalError("Could not locate font named: \(name).\(fileExtension)")
}
do {
let data = try Data(contentsOf: url)
guard let provider = CGDataProvider(data: data as CFData),
let font = CGFont(provider) else {
fatalError("Font appears to be corrupt at: \(url)")
}
var error: Unmanaged<CFError>? = nil
if !CTFontManagerRegisterGraphicsFont(font, &error) {
if let error: Error = error?.takeRetainedValue() {
let nsError = error as NSError
// Continue if already registered
guard nsError.code == CTFontManagerError.alreadyRegistered.rawValue && nsError.domain == kCTFontManagerErrorDomain as String else {
throw error
}
} else {
let info = [NSLocalizedDescriptionKey: NSExceptionName.internalInconsistencyException.rawValue]
throw NSError(domain: Bundle.main.bundleIdentifier!, code: -1, userInfo: info)
}
}
cache.append(name)
} catch {
fatalError(error.localizedDescription)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment