Using fonts from xcfamework

Viewed 320

So I have xcframework which have fonts file (from react-native-vector-icons).

enter image description here

In test project (testApp), I am importing xcframework and I was expecting it to copy resources from my framework but unfortunately it doesn't copy these fonts which are there inside the xcframework and I have to manually add it in Copy Bundle Resources.

For end user, I don't want them to go inside framework and add manually.

In my framework, I have Cops Pods resources in Build Phase but haven't done anything such which would tell the project consuming this framework to copy these fonts (or resources).

What do I need to do so that in project it copies fonts (or resources) by itself? instead of manually doing it?

2 Answers

If you distribute your xcframework using Cocoapods then you can have any resource file automatically copied in the destination .app bundle by Cocoapods.

In your podspec you define the resources you need to copy and the xcframework (and NO spec.sources):

spec.resources = ['Fonts/EncodeSans-ExtraBold.ttf']
spec.vendored_frameworks = 'Example.xcframework'

Or from within the xcframework:

spec.resources = ['Example.xcframework/<a path>/EncodeSans-ExtraBold.ttf']
spec.vendored_frameworks = 'Example.xcframework'

When you execute pod install Cocoapods will create a "[CP] Copy Pods Resources" phase that will copy in the destination app whatever you have defined in the resources:

enter image description here

To avoid collisions(the user or other pod might copy the same file), better use resource_bundles instead of resources.

You need to register the fonts with the font manager. I did this extending UIFont

extension UIFont {
    private class ModuleClass {}
    static func fontsURLs() -> [URL] {
        let bundle = Bundle(for: ModuleClass.self)
        let fileNames = ["Feather"]

        return fileNames.compactMap { fontName in
            bundle.url(forResource: fontName, withExtension: "ttf")
        }
    }
    
    /// Registers the specified graphics font with the font manager.
    /// - Parameter url: Bundle URL of the font to register
    /// - Throws: If font cannot be registered
    static func register(from url: URL) throws {
        guard let fontDataProvider = CGDataProvider(url: url as CFURL) else {
            throw FontError.dataProviderNotCreated(message: "Could not create font data provider for \(url).")
        }
        
        guard let font = CGFont(fontDataProvider) else {
            throw FontError.fontNotCreated(message: "Could not create font data provider for \(url).")
        }
        
        var error: Unmanaged<CFError>?
        guard CTFontManagerRegisterGraphicsFont(font, &error) else {
            throw error?.takeUnretainedValue() ?? FontError.fontNotRegistered
        }
    }
}

Then, you need use this.

public extension UIFont {
/// Registers the specified graphics font with the font manager.
    /// Registered fonts participate in font descriptor matching.
    static func registerFonts() {
        let urls = fontsURLs()
        for url in urls {
            do {
                try register(from: url)
            } catch {
                debugPrint(error)
            }
        }
    }
}

and in your main project (app) register the fonts

// In AppDelegate.swift
import YourModule

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    UIFont.registerFonts()
    return true
}
Related