最小可再现示例(仅使用内置字体):
@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
型
1条答案
按热度按时间y0u0uwnf1#
你似乎对
AppearanceManager.Font
应该存储什么感到困惑。如果它存储了 * 实际的字体名称 *(.custom
可以识别的字体名称),那么FontButton
中的点击手势是不正确的:字符串
name
这里是按钮在其Text
中显示的字体的 * 显示名称 *。如果
AppearanceManager.Font
应该存储显示名称,那么PostView
中的这一点是不正确的:型
您不应该将显示名称传递给
.custom
。"Times New Roman"
在这里恰好是“work”,因为它是字体的家族名称,.custom
可以识别它。TrebuchetMS的家族名称是“Trebuchet MS”(有一个额外的空格)。也可以考虑使用家族名称作为选项。大多数(如果不是全部)家族名称都适合显示。无论如何,您应该决定
AppearanceManager.Font
应该存储什么,并使您的代码与此一致。我建议存储实际的字体名称,并引入一个表示显示名称-字体名称对的结构。
型
除非你出于某种原因不想立即保存字体到用户默认值,否则我建议使用
@AppStorage
。AppearanceManager
看起来像这样:型
在这一点上,如果
AppearanceManager
没有任何其他成员,我会删除AppearanceManager
并直接在视图中使用@AppStorage
。代码的其余部分看起来像这样:
型