Search code examples
iosswiftobjective-ccore-textasset-catalog

What's the definition of Font Asset in the Asset Catalog?


Starting from iOS 13, CTFontManager has the following function:

@discussion Font assets are extracted from the asset catalog and registered. This call must be made after the completion handler of either NSBundleResourceRequest beginAccessingResourcesWithCompletionHandler: or conditionallyBeginAccessingResourcesWithCompletionHandler: is called successfully.
Name the assets using Postscript names for individual faces, or family names for variable/collection fonts. The same names can be used to unregister the fonts with CTFontManagerUnregisterFontDescriptors. In iOS, fonts registered with the persistent scope are not automatically available to other processes. Other process may call CTFontManagerRequestFonts to get access to these fonts.

@param      fontAssetNames
            Array of font name assets in asset catalog.

...

CTFontManagerRegisterFontsWithAssetNames(_ fontAssetNames: CFArray, _ bundle: CFBundle?, _ scope: CTFontManagerScope, _ enabled: Bool, _ registrationHandler: ((CFArray, Bool) -> Bool)?)

However, Asset Catalog does not have any way to add "Font Assets".

What I've tried:

  • took font KanitRegular.ttf (PostScript name is Kanit-Regular) here.
  • created Data Asset named Kanit-Regular in the asset catalog.
  • Renamed font file to Kanit-Regular.ttf and put it into the data asset.

Data Asset's Contents.json now looks like this:

{
   "data" : [
    {
      "filename" : "Kanit-Regular.ttf",
      "idiom": "universal",
      "universal-type-identifier" : "public.truetype-ttf-font"
    }
  ],
  "info" : {
    "author" : "xcode",
    "version" : 1
  }
}
  • Tried to load this font via CTFontManager

like this:

func registerFont() {
    var cfBundle: CFBundle?
    if let bundle = Bundle(for: type(of: self)) {
        cfBundle = CFBundleCreate(kCFAllocatorDefault, bundle.bundleURL as CFURL)
    }
    CTFontManagerRegisterFontsWithAssetNames(["Kanit-Regular"] as CFArray, cfBundle, .persistent, true) { (errors, done) -> Bool in
        print(errors)
        return done
    }
}

After this, getting errors printed:

▿ 1 element
  - 0 : Error Domain=NSPOSIXErrorDomain Code=22 "Invalid argument" UserInfo={CTFontManagerErrorFontAssetNameKey=(
    "Kanit-Regular"
)}

Is there any way to make it work?


Solution

  • Got it working by making the following:

    • Tag the Kanit-Regular data asset with fonts On-Demand Resource Tag (tag name can be the name of your preference, fonts is just the example).
    • Put fonts tag into Initial Install Tags Prefetched Resource Tags section

    Initial Install Tags Resource Tags section

    • Add Fonts Capability in Signing & Capabilities and tick all boxes in it

    enter image description here

    • Implement bundle resource access request before registering fonts

    like this:

    func registerFont() {
        var cfBundle: CFBundle?
        var resourceRequest: NSBundleResourceRequest?
        if let bundle = Bundle(for: type(of: self)) {
            resourceRequest = NSBundleResourceRequest(tags: Set(arrayLiteral: "fonts"), bundle: bundle)
            cfBundle = CFBundleCreate(kCFAllocatorDefault, bundle.bundleURL as CFURL)
        }
        resourceRequest?.beginAccessingResources() { error in
            if let error = error {
                print(error)
            } else {
                CTFontManagerRegisterFontsWithAssetNames(["Kanit-Regular"] as CFArray, cfBundle, .persistent, true) { (errors, done) -> Bool in
                    print(errors)
                    return done
                }
            }
        }
    }
    

    Outcome

    When scope is passed as .persistent or .user, on initial call of CTFontManagerRegisterFontsWithAssetNames function the user will be asked to install fonts into the system, which is not what I really need. In case the scope is .process or .none, the same errors output is returned as provided at the end of the question.

    Although this function does not fit my needs, I at least validated that it is working. Maybe someone finds it useful.