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

问题描述 投票:0回答:1

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

@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()
        }
    }
}

我已将环境对象注入到我的应用程序的根视图中

@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
ios arrays swift fonts
1个回答
0
投票

您似乎对

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"
在这里恰好是“工作”,因为它是字体的家族名称,
.custom
可以识别它。 TrebuchetMS 的家族名称为“Trebuchet MS”(带有一个额外的空格)。还可以考虑使用姓氏作为选项。大多数(如果不是全部)姓氏都适合显示。

无论如何,您应该决定

AppearanceManager.Font
应存储什么,并使您的代码与之一致。

我建议存储实际的字体名称,并引入一个表示显示名称-字体名称对的结构。

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

除非您出于某种原因不想立即将字体保存为用户默认值,否则我建议使用

@AppStorage
AppearanceManager
看起来像:

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
fonts
可以只是一个全局常量。

其余代码如下所示:

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
            }
        }
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.