是否可以在 SwiftUI 中使用带有自定义字体的动态字体大小?
Is it possible to use dynamic type sizes with a custom font in SwiftUI?
我正在使用 SwiftUI,并希望为我的项目使用自定义 UI 字体。但是,我不想丢失内置字体 类(例如 Large Title)附带的动态类型调整大小。
Apple 确实为 Text
:
提供了自定义字体修饰符
Text("Hello, world!")
.font(.custom("Papyrus", size: 17))
但是,这会将大小固定为 17pt。当您在设备或模拟器中 运行 并打开辅助功能检查器以调整 OS 级字体大小时,Text
元素不会更新。
size:
参数不是可选的,所以你必须传入一些东西。不幸的是,您无法获得现有字体(即使是自定义字体)的 size
,因为 Font
没有 size
参数。
这似乎是 Swift 其余部分的常见模式UI,参数可以是可选的,或者您可以传入 nil
以显式禁用某些行为。我希望 .custom()
上的 size:
参数是可选的,并且在内部使用先前 Font
修饰符的大小,或者使用 Text
设置的默认大小。
或者,定义系统样式的静态方法(例如 .largeTitle
)可以接受提供自定义字体名称的参数:.largeTitle("Papyrus")
有人有解决方法吗?
我的方法是创建一个可以绑定到环境大小类别变化的自定义修饰符:
每当你需要使用 Papyrus 时,你会像这样使用它:
Text("Hello World!").modifier(Papyrus())
或者像这样:
Text("Hello World!").modifier(Papyrus(.caption))
Text("Hello World!").modifier(Papyrus(.footnote))
Text("Hello World!").modifier(Papyrus(.subheadline))
Text("Hello World!").modifier(Papyrus(.callout))
Text("Hello World!").modifier(Papyrus())
Text("Hello World!").modifier(Papyrus(.body))
Text("Hello World!").modifier(Papyrus(.headline))
Text("Hello World!").modifier(Papyrus(.title))
Text("Hello World!").modifier(Papyrus(.largeTitle))
您的文本现在无需进一步处理即可动态更改。这是相同的代码,对不同的文本大小偏好做出反应:
你的 Papyrus() 实现看起来像这样。您需要为每个类别找出正确的值,这只是一个示例:
struct Papyrus: ViewModifier {
@Environment(\.sizeCategory) var sizeCategory
var textStyle: Font.TextStyle
init(_ textStyle: Font.TextStyle = .body) {
self.textStyle = textStyle
}
func body(content: Content) -> some View {
content.font(getFont())
}
func getFont() -> Font {
switch(sizeCategory) {
case .extraSmall:
return Font.custom("Papyrus", size: 16 * getStyleFactor())
case .small:
return Font.custom("Papyrus", size: 21 * getStyleFactor())
case .medium:
return Font.custom("Papyrus", size: 24 * getStyleFactor())
case .large:
return Font.custom("Papyrus", size: 28 * getStyleFactor())
case .extraLarge:
return Font.custom("Papyrus", size: 32 * getStyleFactor())
case .extraExtraLarge:
return Font.custom("Papyrus", size: 36 * getStyleFactor())
case .extraExtraExtraLarge:
return Font.custom("Papyrus", size: 40 * getStyleFactor())
case .accessibilityMedium:
return Font.custom("Papyrus", size: 48 * getStyleFactor())
case .accessibilityLarge:
return Font.custom("Papyrus", size: 52 * getStyleFactor())
case .accessibilityExtraLarge:
return Font.custom("Papyrus", size: 60 * getStyleFactor())
case .accessibilityExtraExtraLarge:
return Font.custom("Papyrus", size: 66 * getStyleFactor())
case .accessibilityExtraExtraExtraLarge:
return Font.custom("Papyrus", size: 72 * getStyleFactor())
@unknown default:
return Font.custom("Papyrus", size: 36 * getStyleFactor())
}
}
func getStyleFactor() -> CGFloat {
switch textStyle {
case .caption:
return 0.6
case .footnote:
return 0.7
case .subheadline:
return 0.8
case .callout:
return 0.9
case .body:
return 1.0
case .headline:
return 1.2
case .title:
return 1.5
case .largeTitle:
return 2.0
@unknown default:
return 1.0
}
}
}
更新
我修改了实现以接受文本样式作为参数。
如果您想保持 SwiftUI-like 风格,您可以为 UIKit-compatible 平台扩展 Font
:
import SwiftUI
extension Font {
#if canImport(UIKit)
static var myHeadline = Font.custom(
"Your-Font-Name",
size: UIFontMetrics(forTextStyle: .headline).scaledValue(for: 17)
)
#endif
}
然后,使用它:
Text("Hello World!")
.font(.myHeadline)
请注意,除非您重新启动应用程序,否则您的自定义字体不会更新。这意味着 canvas 预览不会以这种方式工作。
我会在有空的时候进一步调查这个话题。
(另外,这应该是原生功能。如果有Font.system(_ style: Font.TextStyle, design: Font.Design = .default)
,也应该有Font.custom(_ name: String, style: Font.TextStyle)
。请参阅反馈中的FB6523689。)
我也通过 ViewModifier
偶然发现了一种实现此目的的好方法。我从 this Hacking With Swift's article 借用了动态类型和自定义字体的基本修饰符。结果如下:
import SwiftUI
@available(iOS 13, macCatalyst 13, tvOS 13, watchOS 6, *)
struct CustomFont: ViewModifier {
@Environment(\.sizeCategory) var sizeCategory
var name: String
var style: UIFont.TextStyle
var weight: Font.Weight = .regular
func body(content: Content) -> some View {
return content.font(Font.custom(
name,
size: UIFont.preferredFont(forTextStyle: style).pointSize)
.weight(weight))
}
}
@available(iOS 13, macCatalyst 13, tvOS 13, watchOS 6, *)
extension View {
func customFont(
name: String,
style: UIFont.TextStyle,
weight: Font.Weight = .regular) -> some View {
return self.modifier(CustomFont(name: name, style: style, weight: weight))
}
}
和用法:
Text("Hello World!")
.customFont(name: "Georgia", style: .headline, weight: .bold)
这样您就可以坚持使用捆绑的文本样式,而无需明确提供大小。如果您想这样做, font
修饰符已经允许我们这样做,并且可以通过针对此问题给出的一种替代方法来处理缩放。
此外,请注意,由于样式是在 ViewModifier
符合 struct
中应用的,它反过来响应环境 sizeCategory
的变化,视图将反映对切换回您的应用程序后的辅助功能设置;所以不需要重新启动它。
我正在使用 SwiftUI,并希望为我的项目使用自定义 UI 字体。但是,我不想丢失内置字体 类(例如 Large Title)附带的动态类型调整大小。
Apple 确实为 Text
:
Text("Hello, world!")
.font(.custom("Papyrus", size: 17))
但是,这会将大小固定为 17pt。当您在设备或模拟器中 运行 并打开辅助功能检查器以调整 OS 级字体大小时,Text
元素不会更新。
size:
参数不是可选的,所以你必须传入一些东西。不幸的是,您无法获得现有字体(即使是自定义字体)的 size
,因为 Font
没有 size
参数。
这似乎是 Swift 其余部分的常见模式UI,参数可以是可选的,或者您可以传入 nil
以显式禁用某些行为。我希望 .custom()
上的 size:
参数是可选的,并且在内部使用先前 Font
修饰符的大小,或者使用 Text
设置的默认大小。
或者,定义系统样式的静态方法(例如 .largeTitle
)可以接受提供自定义字体名称的参数:.largeTitle("Papyrus")
有人有解决方法吗?
我的方法是创建一个可以绑定到环境大小类别变化的自定义修饰符:
每当你需要使用 Papyrus 时,你会像这样使用它:
Text("Hello World!").modifier(Papyrus())
或者像这样:
Text("Hello World!").modifier(Papyrus(.caption))
Text("Hello World!").modifier(Papyrus(.footnote))
Text("Hello World!").modifier(Papyrus(.subheadline))
Text("Hello World!").modifier(Papyrus(.callout))
Text("Hello World!").modifier(Papyrus())
Text("Hello World!").modifier(Papyrus(.body))
Text("Hello World!").modifier(Papyrus(.headline))
Text("Hello World!").modifier(Papyrus(.title))
Text("Hello World!").modifier(Papyrus(.largeTitle))
您的文本现在无需进一步处理即可动态更改。这是相同的代码,对不同的文本大小偏好做出反应:
你的 Papyrus() 实现看起来像这样。您需要为每个类别找出正确的值,这只是一个示例:
struct Papyrus: ViewModifier {
@Environment(\.sizeCategory) var sizeCategory
var textStyle: Font.TextStyle
init(_ textStyle: Font.TextStyle = .body) {
self.textStyle = textStyle
}
func body(content: Content) -> some View {
content.font(getFont())
}
func getFont() -> Font {
switch(sizeCategory) {
case .extraSmall:
return Font.custom("Papyrus", size: 16 * getStyleFactor())
case .small:
return Font.custom("Papyrus", size: 21 * getStyleFactor())
case .medium:
return Font.custom("Papyrus", size: 24 * getStyleFactor())
case .large:
return Font.custom("Papyrus", size: 28 * getStyleFactor())
case .extraLarge:
return Font.custom("Papyrus", size: 32 * getStyleFactor())
case .extraExtraLarge:
return Font.custom("Papyrus", size: 36 * getStyleFactor())
case .extraExtraExtraLarge:
return Font.custom("Papyrus", size: 40 * getStyleFactor())
case .accessibilityMedium:
return Font.custom("Papyrus", size: 48 * getStyleFactor())
case .accessibilityLarge:
return Font.custom("Papyrus", size: 52 * getStyleFactor())
case .accessibilityExtraLarge:
return Font.custom("Papyrus", size: 60 * getStyleFactor())
case .accessibilityExtraExtraLarge:
return Font.custom("Papyrus", size: 66 * getStyleFactor())
case .accessibilityExtraExtraExtraLarge:
return Font.custom("Papyrus", size: 72 * getStyleFactor())
@unknown default:
return Font.custom("Papyrus", size: 36 * getStyleFactor())
}
}
func getStyleFactor() -> CGFloat {
switch textStyle {
case .caption:
return 0.6
case .footnote:
return 0.7
case .subheadline:
return 0.8
case .callout:
return 0.9
case .body:
return 1.0
case .headline:
return 1.2
case .title:
return 1.5
case .largeTitle:
return 2.0
@unknown default:
return 1.0
}
}
}
更新
我修改了实现以接受文本样式作为参数。
如果您想保持 SwiftUI-like 风格,您可以为 UIKit-compatible 平台扩展 Font
:
import SwiftUI
extension Font {
#if canImport(UIKit)
static var myHeadline = Font.custom(
"Your-Font-Name",
size: UIFontMetrics(forTextStyle: .headline).scaledValue(for: 17)
)
#endif
}
然后,使用它:
Text("Hello World!")
.font(.myHeadline)
请注意,除非您重新启动应用程序,否则您的自定义字体不会更新。这意味着 canvas 预览不会以这种方式工作。
我会在有空的时候进一步调查这个话题。
(另外,这应该是原生功能。如果有Font.system(_ style: Font.TextStyle, design: Font.Design = .default)
,也应该有Font.custom(_ name: String, style: Font.TextStyle)
。请参阅反馈中的FB6523689。)
我也通过 ViewModifier
偶然发现了一种实现此目的的好方法。我从 this Hacking With Swift's article 借用了动态类型和自定义字体的基本修饰符。结果如下:
import SwiftUI
@available(iOS 13, macCatalyst 13, tvOS 13, watchOS 6, *)
struct CustomFont: ViewModifier {
@Environment(\.sizeCategory) var sizeCategory
var name: String
var style: UIFont.TextStyle
var weight: Font.Weight = .regular
func body(content: Content) -> some View {
return content.font(Font.custom(
name,
size: UIFont.preferredFont(forTextStyle: style).pointSize)
.weight(weight))
}
}
@available(iOS 13, macCatalyst 13, tvOS 13, watchOS 6, *)
extension View {
func customFont(
name: String,
style: UIFont.TextStyle,
weight: Font.Weight = .regular) -> some View {
return self.modifier(CustomFont(name: name, style: style, weight: weight))
}
}
和用法:
Text("Hello World!")
.customFont(name: "Georgia", style: .headline, weight: .bold)
这样您就可以坚持使用捆绑的文本样式,而无需明确提供大小。如果您想这样做, font
修饰符已经允许我们这样做,并且可以通过针对此问题给出的一种替代方法来处理缩放。
此外,请注意,由于样式是在 ViewModifier
符合 struct
中应用的,它反过来响应环境 sizeCategory
的变化,视图将反映对切换回您的应用程序后的辅助功能设置;所以不需要重新启动它。