ios 某些字体不适用于特定视图

ijnw1ujt  于 12个月前  发布在  iOS
关注(0)|答案(1)|浏览(118)

最小可再现示例(仅使用内置字体):

@main
struct FontExampleApp: App {
    @StateObject private var appearanceManager = AppearanceManager()

    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(appearanceManager)

        }
    }
}

class AppearanceManager: ObservableObject {

    @Published var Font: String = "System" {
        didSet {
            saveFont()
            objectWillChange.send()
            print("Set font to \(Font)")
            print("Corresponding font name: \(fonts[Font])")
        }
    }
    
    public let fonts = ["System" : "system-default", // unrecognized name defaults to system font
                        "Times New Roman" : "TimesNewRomanPSMT",
                        "Trebuchet" : "TrebuchetMS"
    ]
    
    init() {
        loadFont()
        
        for family in UIFont.familyNames.sorted() {
            let names = UIFont.fontNames(forFamilyName: family)
            print("Family: \(family) Font names: \(names)")
        }
    }
    
    private let fontKey = "FontKey"
    
    private func saveFont() {
        UserDefaults.standard.set(Font, forKey: fontKey)
    }
    
    private func loadFont() {
        if let savedFont = UserDefaults.standard.value(forKey: fontKey) as? String {
            Font = savedFont
        }
    }
}

struct ContentView: View {
    var body: some View {
        VStack {
            PostView()
            Spacer()
            FontSelectionView()
        }
        .padding()
    }
}

struct PostView: View {
    @EnvironmentObject var appearanceManager: AppearanceManager
    
    var body: some View {
        ScrollView {
            VStack(alignment: .leading, spacing: 16) {
                Text("Hello World")
                // below only works for certain fonts
                    .font(.custom(appearanceManager.Font, size: 30, relativeTo: .title))
                
            }
            .padding()
        }
    }
}

struct FontSelectionView: View {
    @EnvironmentObject var appearanceManager: AppearanceManager

    var body: some View {
        Section(header: Text("Fonts")) {
            VStack(alignment: .leading) {
                ForEach(appearanceManager.fonts.keys.sorted(), id: \.self) { fontName in
                    FontButton(name: fontName)
                }
                .padding(.vertical, 5)
            }
        }
    }
}

struct FontButton: View {
    @EnvironmentObject var appearanceManager: AppearanceManager

    let name: String
    var isSelected: Bool {
        appearanceManager.Font == name
    }

    var body: some View {
        HStack {
            Text(name)
                .font(.custom(appearanceManager.fonts[name] ?? "", size: 17, relativeTo: .body))
            Spacer()
            Circle()
                .frame(width: 20, height: 20)
                .foregroundColor(isSelected ? .secondary : .clear)
                .overlay(
                    Circle()
                        .stroke(Color.primary, lineWidth: 2)
                )
        }
        .foregroundColor(.secondary)
        .onTapGesture {
            withAnimation {
                appearanceManager.Font = name
            }
        }
    }
}

字符串
上面的例子只使用了两种自定义字体,这两种字体都是内置的。请注意,Times New Roman可以按预期工作,但Trebuchet不能。
这个最小的可复制示例扩展到下面描述的情况,其中涉及内置字体和第三方字体。
我已经在我的项目中添加了自定义字体,并且已经成功地使用每种字体对文本进行样式化,但只能在设置菜单中使用:

class AppearanceManager: ObservableObject {

    @Published var Font: String = "System" {
        didSet {
            saveFont()
            objectWillChange.send()
            print("Set font to \(Font)")
            print("Corresponding font name: \(fonts[Font])")
        }
    }
    
    public let fonts = ["System" : "system-default", // unrecognized name defaults to system font
                        "Courier Prime" : "CourierPrime-Regular",
                        "JetBrains Mono" : "JetBrainsMono-Regular",
                        "Roboto" : "RobotoMono-Regular",
                        "Ubuntu" : "UbuntuMono-Regular",
                        "Victor" : "VictorMono-Regular",
                        "Times New Roman" : "TimesNewRomanPSMT",
                        "Trebuchet" : "TrebuchetMS"
    ]
    
    init() {
        loadFont()
        
        // used for debugging
        for family in UIFont.familyNames.sorted() {
            let names = UIFont.fontNames(forFamilyName: family)
            print("Family: \(family) Font names: \(names)")
        }
    }
    
    private let fontKey = "FontKey"
    
    private func saveFont() {
        UserDefaults.standard.set(Font, forKey: fontKey)
    }
    
    private func loadFont() {
        if let savedFont = UserDefaults.standard.value(forKey: fontKey) as? String {
            Font = savedFont
        }
    }
}

struct SettingsView: View {
    @EnvironmentObject var appearanceManager: AppearanceManager
    
    var body: some View {
        Form {
            FontSelectionView()
        }
        .navigationTitle("Settings")
    }
}

struct FontSelectionView: View {
    @EnvironmentObject var appearanceManager: AppearanceManager

    var body: some View {
        Section(header: Text("Fonts")) {
            VStack(alignment: .leading) {
                ForEach(appearanceManager.fonts.keys.sorted(), id: \.self) { fontName in
                    FontButton(name: fontName)
                }
                .padding(.vertical, 5)
            }
        }
    }
}

struct FontButton: View {
    @EnvironmentObject var appearanceManager: AppearanceManager

    let name: String
    var isSelected: Bool {
        appearanceManager.Font == name
    }

    var body: some View {
        HStack {
            Text(name)
                .font(.custom(appearanceManager.fonts[name] ?? "", size: 17, relativeTo: .body))
            Spacer()
            Circle()
                .frame(width: 20, height: 20)
                .foregroundColor(isSelected ? .secondary : .clear)
                .overlay(
                    Circle()
                        .stroke(Color.primary, lineWidth: 2)
                )
        }
        .foregroundColor(.secondary)
        .onTapGesture {
            withAnimation {
                appearanceManager.Font = name
            }
        }
    }
}


上面的代码可用于将每种字体应用于文本:

然而,某些字体(不仅仅是我添加到项目中的自定义字体,还有内置字体,如Trebuchet)并没有应用到整个应用程序的文本元素中:

struct PostView: View {
    @EnvironmentObject var appearanceManager: AppearanceManager
    
    var body: some View {
        ScrollView {
            VStack(alignment: .leading, spacing: 16) {
                
                Text("Hello World")
                    // below only works for certain fonts
                    .font(.custom(appearanceManager.Font, size: 30, relativeTo: .title))

            }
            .padding()
        }
    }
}


我已经将EnvironmentObject注入到应用程序的根视图中

@main
struct MyApp: App {
    
    @StateObject private var appearanceManager = AppearanceManager()

    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(appearanceManager)
        }
    }
}


我还打印了所有可用的字体,并确定实际的字体名称正在发布:

// prints in init() method: 
...
Family: Times New Roman Font names: ["TimesNewRomanPSMT", "TimesNewRomanPS-ItalicMT", "TimesNewRomanPS-BoldMT", "TimesNewRomanPS-BoldItalicMT"]
Family: Trebuchet MS Font names: ["TrebuchetMS", "TrebuchetMS-Italic", "TrebuchetMS-Bold", "Trebuchet-BoldItalic"]
Family: Ubuntu Mono Font names: ["UbuntuMono-Regular"]
Family: Verdana Font names: ["Verdana", "Verdana-Italic", "Verdana-Bold", "Verdana-BoldItalic"]
Family: Victor Mono Font names: ["VictorMono-Regular"]
...

// prints when I set a font: 

Set font to Trebuchet
Corresponding font name: Optional("TrebuchetMS") // Not applied outside of settings menu

Set font to JetBrains Mono
Corresponding font name: Optional("JetBrainsMono-Regular") // works as expected

Set font to Roboto
Corresponding font name: Optional("RobotoMono-Regular") // not applied outside of settings menu

y0u0uwnf

y0u0uwnf1#

你似乎对AppearanceManager.Font应该存储什么感到困惑。如果它存储了 * 实际的字体名称 *(.custom可以识别的字体名称),那么FontButton中的点击手势是不正确的:

.onTapGesture {
    withAnimation {
        appearanceManager.Font = name
    }
}

字符串
name这里是按钮在其Text中显示的字体的 * 显示名称 *。
如果AppearanceManager.Font应该存储显示名称,那么PostView中的这一点是不正确的:

.font(.custom(appearanceManager.Font, size: 30, relativeTo: .title))


您不应该将显示名称传递给.custom
"Times New Roman"在这里恰好是“work”,因为它是字体的家族名称,.custom可以识别它。TrebuchetMS的家族名称是“Trebuchet MS”(有一个额外的空格)。也可以考虑使用家族名称作为选项。大多数(如果不是全部)家族名称都适合显示。
无论如何,您应该决定AppearanceManager.Font应该存储什么,并使您的代码与此一致。
我建议存储实际的字体名称,并引入一个表示显示名称-字体名称对的结构。

struct FontOption: Identifiable, Hashable{
    let fontName: String
    let displayName: String
    
    var id: String { fontName }
}


除非你出于某种原因不想立即保存字体到用户默认值,否则我建议使用@AppStorageAppearanceManager看起来像这样:

class AppearanceManager: ObservableObject {
    
    @AppStorage("FontKey") var font: String = ""
    
    public let fonts = [
        FontOption(fontName: "", displayName: "System"),
        FontOption(fontName: "TimesNewRomanPSMT", displayName: "Times New Roman"),
        FontOption(fontName: "TrebuchetMS", displayName: "Trebuchet"),
    ]
}


在这一点上,如果AppearanceManager没有任何其他成员,我会删除AppearanceManager并直接在视图中使用@AppStorage
代码的其余部分看起来像这样:

struct PostView: View {
    @EnvironmentObject var appearanceManager: AppearanceManager
    
    var body: some View {
        ScrollView {
            VStack(alignment: .leading, spacing: 16) {
                Text("Hello World")
                    .font(.custom(appearanceManager.font, size: 30, relativeTo: .title))
                
            }
            .padding()
        }
    }
}

struct FontSelectionView: View {
    @EnvironmentObject var appearanceManager: AppearanceManager
    
    var body: some View {
        Section(header: Text("Fonts")) {
            VStack(alignment: .leading) {
                ForEach(appearanceManager.fonts) { font in
                    FontButton(font: font)
                }
                .padding(.vertical, 5)
            }
        }
    }
}

struct FontButton: View {
    @EnvironmentObject var appearanceManager: AppearanceManager
    
    let font: FontOption
    
    var isSelected: Bool {
        appearanceManager.font == font.fontName
    }
    
    var body: some View {
        HStack {
            Text(font.displayName)
                .font(.custom(font.fontName, size: 17, relativeTo: .body))
            Spacer()
            Circle()
                .frame(width: 20, height: 20)
                .foregroundColor(isSelected ? .secondary : .clear)
                .overlay(
                    Circle()
                        .stroke(Color.primary, lineWidth: 2)
                )
        }
        .foregroundColor(.secondary)
        .onTapGesture {
            withAnimation {
                appearanceManager.font = font.fontName
            }
        }
    }
}

相关问题