Custom Font Picker Accessing UIFont Properties Crashes App

I am attempting to create a custom font picker (similar to the one in Pages) using SwiftUI, because the current UIFontPickerViewController isn't sufficient enough for my app.

When running the app and presenting FontPickerView in a sheet, the app seems to pause, the sheet doesn't appear, and a lot of dialog continuously pops up in the Xcode console.

This is an example of what keeps showing:

CoreText note: Client requested name ".SFUI-Regular", it will get TimesNewRomanPSMT rather than the intended font. All system UI font access should be through proper APIs such as CTFontCreateUIFontForLanguage() or +[UIFont systemFontOfSize:].

The .SFUI string changes with different font styles, for example -Bold, -Compressed, -SemiExpandedLight...

I believe the problem lies when accessing the UIFont family and font name properties and methods.

Is there a reason why this is happening? A possible solution?

Help is appreciated.

Here is some code you can test (Xcode 13 beta 3):

extension Character {
    var isUppercase: Bool {
        String(self).uppercased() == String(self)
    }
}

extension UIFont {
    // Will show dialog from here
    class func familyName(forFontName fontName: String) -> String {
        var familyName = ""

        familyNames.forEach { family in
            fontNames(forFamilyName: family).forEach { font in
                if font == fontName {
                    familyName = family
                }
            }
        }

        return familyName
    }
}

struct FontPickerView: View {
    @Environment(\.dismiss) private var dismiss

    @ScaledMetric private var linkPaddingLength = 24

    @State private var searchText = ""
    @State private var linkSelection: String?
    @Binding var selectedFontName: String

    // Will show dialog from here
    private var familyNames: [String] {
        UIFont.familyNames.filter {
            $0.contains(searchText) || searchText.isEmpty
        }
    }

    // Will show dialog from here
    private var familyPickerBinding: Binding<String> {
        Binding {
            UIFont.familyName(forFontName: selectedFontName)
        } set: {
            selectedFontName = UIFont.fontNames(forFamilyName: $0)[0]
        }
    }

    var body: some View {
        NavigationView {
            List {
                Picker("All Fonts", selection: familyPickerBinding) {
                    ForEach(familyNames, id: \.self, content: pickerRow)
                }
                .labelsHidden()
                .pickerStyle(.inline)
            }
            .listStyle(.insetGrouped)
            .searchable(text: $searchText, placement: .navigationBarDrawer)
            .navigationTitle("Fonts")
            .navigationBarTitleDisplayMode(.inline)
            .toolbar {
                Button("Cancel", action: dismiss.callAsFunction)
            }
        }
    }

    private func linkPadding(forFamilyName familyName: String) -> CGFloat {
        familyPickerBinding.wrappedValue == familyName ? 0 : linkPaddingLength
    }

    private func familyText(forFamilyName familyName: String) -> some View {
        Text(familyName)
            .font(.custom(familyName, size: UIFont.labelFontSize))
    }

    private func familyTextWithStyles(forFamilyName familyName: String) -> some View {
        ZStack(alignment: .leading) {
            NavigationLink("Style options", tag: familyName, selection: $linkSelection) {
                FontStylesView(selection: $selectedFontName, family: familyName)
            }
            .hidden()

            HStack {
                familyText(forFamilyName: familyName)

                Spacer()

                Button(action: { linkSelection = familyName }) {
                    Label("Style options", systemImage: "info.circle")
                        .labelStyle(.iconOnly)
                        .imageScale(.large)
                }
                .buttonStyle(.borderless)
                .padding(.trailing, linkPadding(forFamilyName: familyName))
            }
        }
    }

    private func pickerRow(forFamilyName familyName: String) -> some View {
        Group {
            if UIFont.fontNames(forFamilyName: familyName).count == 1 {
                familyText(forFamilyName: familyName)
            } else {
                familyTextWithStyles(forFamilyName: familyName)
            }
        }
    }
}

struct FontStylesView: View {
    @Binding var selection: String

    let family: String

    var body: some View {
        List {
            Picker("Font Styles", selection: $selection) {
                // Will show dialog from here
                ForEach(UIFont.fontNames(forFamilyName: family), id: \.self) { font in
                    Text(fontType(forFontName: font))
                        .font(.custom(font, size: UIFont.labelFontSize))
                }
            }
            .labelsHidden()
            .pickerStyle(.inline)
        }
        .navigationTitle(family)
    }

    private func fontType(forFontName fontName: String) -> String {
        if let index = fontName.lastIndex(of: "-") {
            var text = String(fontName.suffix(from: index).dropFirst())

            // Add spaces between words.

            let indexes = text.enumerated().filter { $0.element.isUppercase }.map { $0.offset }

            var count = 0
            indexes.forEach { index in
                guard index > 0 else { return }
                text.insert(" ", at: text.index(text.startIndex, offsetBy: index + count))
                count += 1
            }

            return text
        } else {
            return "Regular"
        }
    }
}

(There are a few bugs that I am going to fix.)

Update: I've managed to narrow down the problem even more. There is a new font family called System Font that I haven't seen before. It contains lots of font names that are replaced by TimesNewRomanPSMT which is what all the dialog appearing in the console is about.

For now I have created a new property on UIFont that will remove this font family, and now no more text is outputted in the console.

class var filteredFamilyNames: [String] {
    var filtered = Self.familyNames
    filtered.removeAll { $0 == "System Font" }
    return filtered
}

Is this font family a new thing in iOS 15? Why are there so many fonts inside this family that Core Text can't use?

While I am still here, can I ask if there is a better way to get the family name that a particular font is in. The updated version of this method on UIFont works, but it has a cost on performance. When interacting with the font picker, it takes about a second for the UI to update.

class func familyName(forFontName fontName: String) -> String {
    var familyName = ""

    filteredFamilyNames.forEach { family in
        fontNames(forFamilyName: family).forEach { font in
           if font == fontName {
                familyName = family
            }
        }
    }

    return familyName
}

Thank you to anyone who can help with this matter.

Custom Font Picker Accessing UIFont Properties Crashes App
 
 
Q