Fonts randomly stop rendering correctly on iOS 17/Xcode 15

Hello

After updating my/our devices to iOS 17 (or XCode 15, not entirely sure), the custom fonts in our application have started randomly not working. Usually it will happen after the app has been in the background for a while.

What happens is that:

  1. The fonts either don't load at all. For icon fonts, this means a lot of question marks.
  2. The fonts get swapped around, meaning regular text (Roboto, custom font) starts rendering using the icon font (Font Awesome). I can tell because I recognize the font from development.

I have no idea how to reproduce it. I tried simulating the memory warning on the simulator, but that doesn't trigger it. It did not happen when building for iOS 16 and I did not change any font logic since then. It still does not happen on devices running iOS 17 but on versions of the app built with XCode 14.

Some debug info:

XCode: 15.0.1 (15A507)

iOS: 17.1.1 (real device, I have not observed it on Simulator)

We load the custom fonts from a proprietary SPM package:

fileprivate static func registerFont(fontName: String, ext: String = "otf") {
    
    guard let fontURL = Bundle.module.url(forResource: fontName, withExtension: ext),
        let fontDataProvider = CGDataProvider(url: fontURL as CFURL),
        let font = CGFont(fontDataProvider) else {
            fatalError("Couldn't create font from filename: \(fontName).\(ext)")
    }
    
    var error: Unmanaged<CFError>?

    CTFontManagerRegisterGraphicsFont(font, &error)
    
}

public static func registerFonts() {

    registerFont(fontName: "Font Awesome 6 Brands-Regular-400")
    registerFont(fontName: "Font Awesome 6 Duotone-Solid-900")
    registerFont(fontName: "Font Awesome 6 Pro-Light-300")
    registerFont(fontName: "Font Awesome 6 Pro-Regular-400")
    registerFont(fontName: "Font Awesome 6 Pro-Solid-900")
    registerFont(fontName: "Font Awesome 6 Pro-Thin-100")
    registerFont(fontName: "Font Awesome 6 Sharp-Light-300")
    registerFont(fontName: "Font Awesome 6 Sharp-Regular-400")
    registerFont(fontName: "Font Awesome 6 Sharp-Solid-900")
    registerFont(fontName: "Roboto-Medium", ext: "ttf")
    registerFont(fontName: "Roboto-Regular", ext: "ttf")
    registerFont(fontName: "RobotoMono-Regular", ext: "ttf")
    
}

// Similar methods for all font types and icons:
@objc public static func getDefaultFont(size: CGFloat) -> UIFont {

    return UIFont.init(name: "Roboto-Regular", size: size) ?? UIFont.systemFont(ofSize: size);

}

This code is the called in didFinishLaunching, and it would crash the app if it failed:

FontHandler.registerFonts()

To reiterate, all the fonts work when launching the app. Restarting the app always fixes it, which, combined with the fact that they get swapped around, leads me to believe that it's some kind of font caching issue.

We use the fonts in code-only (no storyboards or xibs ever). A font would be loaded like this:

let label = UILabel()
label.font = FontHandler.getDefaultFont(size: 15)

The font files ship with the SPM package itself, which contains the FontHandler class used above.

import PackageDescription

let package = Package(
    name: "MyPackageName",
    defaultLocalization: "en",
    platforms: [
        .iOS(.v11)
    ],
    products: [
        .library(
            name: "MyPackageName",
            targets: ["MyPackageName"]),
    ],
    targets: [
        .target(
            name: "MyPackageName",
            resources: [
                .process("Fonts")
            ]),
        .testTarget(
            name: "MyPackageNameTests",
            dependencies: ["MyPackageName"]),
    ]
)

is there any solution?

We're facing the same issue, this was supposed to be fixed in the latest version of Xcode 15.0.1 (https://developer.apple.com/documentation/xcode-release-notes/xcode-15_0_1-release-notes) but we're still seeing it happen randomly.

I'm having this issue as well on iOS 17.2.1 and Xcode 15.2.0. Certain fonts (Font Awesome 5 Pro Light in my case) will randomly return nil from UIFont(fontWithName:_,size:_). Even when the font name is returned through UIFont.fontNames(forFamilyName:_) The strange thing is that I can reliably reproduce it, however it doesn't always happen. In other words, I can navigate to a screen that uses it just fine, but if I take a different route through the app to get there, then it stops working.

Same issue here. Custom fonts in SwiftUI views stops rendering after usin app for a while and some default platform font is shown instead. This is weird because fonts are shown correctly at first.

In UIKit views fonts are shown correctly.

I just would like to say that I still have this problem on Xcode 15.2. I had hoped it would go away with 15.2, but no. No idea how to proceed or where to begin debugging this.

Is there any update on this? or if there is a workaround. Please suggest.

We have the same issue with latest iOS version 17.2.1

I also have this problem, but can trigger the issue to occur reliably by using the iOS Simulator "Simulate Memory Warning" function. This would seem to suggest that when the device is memory constrained it unloads fonts from memory, but then doesn't do a good job of reloading them again when they're needed.

Getting this today using the latest Xcode 15.2

Ok. I may have found a fix for this that others may be able to make use of. Thanks to @jadardev as your comment above gave me the seed of what I needed.

The font I was working with was Font Awesome, and I was loading it like this:

label.font = [UIFont fontWithName:@"Font Awesome 6 Pro Regular" size:17];

I got this font name by inspecting the file, and it was working just fine except for intermittently failing linked to low memory as other posters have observed. Having spotted this I starting experimenting and noticed using UIFont.fontNames(forFamilyName:_) that actually this font was described differently as FontAwesome6Pro-Regular.

I switched to referring to the font by that name and weirdly, that doesn't seem to experience the problem. You could argue that i'd been using the wrong font name the whole time, but surely then it would never have worked. In any case, i'm happy because this now seems to work and hope this solution helps others.

I was facing a similar problem: Some fonts were loaded and rendered properly, but later, after some navigation, they started to render incorrectly (falling back to the system font). I checked all the fonts used, and the problem only occurred with fonts lacking 'name tables in the font.'

Fontname: null
Family Name: null
Name For Humans: null

The solution was to write that information into the font, and we haven't had any problems since. I used this script: https://github.com/chrissimpkins/fontname.py

Execution (your experience may vary):

python3 -m venv ./venv
source ./venv/bin/activate
python3 -m pip install fonttools

python3 fontname.py 'My Font Name' './My Font Name.otf'

Cleanup:

deactivate
rm -rf ./venv
python3 -m pip uninstall fonttools

I think I may have found a solution.

None of the suggestions helped, as I already used the correct PostScript font name and all fonts had a name.

The original code that loaded the font looked like this, as in my original post:

guard let fontURL = Bundle.module.url(forResource: fontName, withExtension: ext),
  let fontDataProvider = CGDataProvider(url: fontURL as CFURL),
  let font = CGFont(fontDataProvider) else {
    fatalError("Couldn't create font from filename: \(fontName).\(ext)")
}
    
var error: Unmanaged<CFError>?

CTFontManagerRegisterGraphicsFont(font, &error)

Which appears fine. It was code I copied from StackOverflow, and since it worked I just let it be. Now I spent some time reading about this method (CTFontManagerRegisterGraphicsFont), and it seems it has a sibling, called CTFontManagerRegisterFontsForURL (https://developer.apple.com/documentation/coretext/1499468-ctfontmanagerregisterfontsforurl). The main difference between these two methods, aside from one asking for a URL to the font file and another asking for the actual font, is that the latter takes another argument, namely scope, which can be one of none, process, persistent and session - see https://developer.apple.com/documentation/coretext/ctfontmanagerscope.

So I changed my code to this, using the process scope:

guard let fontURL = Bundle.module.url(forResource: fontName, withExtension: ext) else {
    fatalError("Couldn't create font from filename: \(fontName).\(ext)")
}

var error: Unmanaged<CFError>?

CTFontManagerRegisterFontsForURL(fontURL as CFURL, .process, &error)

And it now seems it's resolved. I have not seen the fonts misbehaving since, and it's been several days of opening the app, working on it and such. I also want to clarify that in my original post, it was simply a case of UIFont returning nil for the requested font, and I had null coalesced a nil font to the system default, which added to my confusion with regard to the "swapping around" of fonts - something that did not happen.

I realise this does not help anyone not loading fonts from code like this.

It happened with me as well on Xcode 15.4. I tried all the solutions listed above plus other solutions mentioned on developer forum. At the end the best workaround to solve this issue was to add fonts using @IBInspectable

Check my answer here : https://stackoverflow.com/a/78921502/17610775

Fonts randomly stop rendering correctly on iOS 17/Xcode 15
 
 
Q