在SwiftUI中可以使用带有自定义字体的动态类型大小吗?

时间:2019-06-23 18:45:55

标签: swift user-interface fonts swiftui

我正在使用SwiftUI,并且想为我的项目使用自定义UI字体。但是,我不想失去内置字体类(例如大标题)附带的动态类型大小调整。

Apple确实为Text提供了自定义字体修饰符:

Text("Hello, world!")
    .font(.custom("Papyrus", size: 17))

但是,这会将尺寸固定为17pt。当您在设备上或模拟器中运行此命令并打开辅助功能检查器以调整操作系统级别的字体大小时,Text元素不会更新。

size:参数不是可选的,因此您必须传递一些内容。而且不幸的是,您无法获得现有字体的size(甚至是自定义字体),因为Font没有size参数。

在SwiftUI的其余部分中,参数可以是可选的,或者您可以传入nil来显式禁用某些行为,这似乎是一种常见的模式。我希望size:上的.custom()参数是可选的,并且在内部使用上一个Font修饰符中的尺寸,或使用Text设置的默认尺寸。

或者,定义系统样式的静态方法(例如.largeTitle)可以接受提供自定义字体名称的参数:.largeTitle("Papyrus")

有人可以解决吗?

3 个答案:

答案 0 :(得分:2)

我要做的方式是创建一个可以绑定到环境大小类别更改的自定义修饰符:

每当需要使用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))

您的文本现在将动态更改,无需进一步工作。这是相同的代码,对不同的文本大小首选项有反应:

enter image description here

您的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
        }
    }

}

更新

我修改了实现以接受文本样式作为参数。

答案 1 :(得分:0)

如果您想保留类似SwiftUI的样式,则可以针对兼容UIKit的平台扩展 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)

请注意,除非重新启动应用程序,否则自定义字体将不会更新。这意味着画布预览无法通过这种方式工作。

只要有时间,我就会对此主题进行进一步的调查。

(此外,这应该是本机功能。如果有Font.system(_ style: Font.TextStyle, design: Font.Design = .default),也应该有Font.custom(_ name: String, style: Font.TextStyle)。请参见“反馈”中的FB6523689。)

答案 2 :(得分:0)

我偶然发现了一种很好的方法也可以通过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的{​​{1}}内应用的,而struct则响应环境sizeCategory的更改,因此视图将反映对可访问性的更改设置切换回您的应用程序时;因此无需重新启动它。