【问题标题】:Is it possible to use dynamic type sizes with a custom font in SwiftUI?是否可以在 SwiftUI 中使用带有自定义字体的动态字体大小?
【发布时间】:2019-11-05 16:30:40
【问题描述】:

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

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

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

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

size: 参数不是可选的,所以你必须传入一些东西。不幸的是,您无法获得现有字体(甚至是自定义字体)的size,因为Font 没有size 参数。

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

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

有人有解决方法吗?

【问题讨论】:

    标签: swift user-interface fonts swiftui


    【解决方案1】:

    我的做法是创建一个自定义修饰符,可以绑定到环境大小类别的变化:

    当你需要使用 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
            }
        }
    
    }
    

    更新

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

    【讨论】:

    • 我修改了实现以接受文本样式作为参数。
    • 这是一个很好的解决方法——干得好! ? 不过,我希望我们能得到一个更好的第一方解决方案,因为将所有这些点大小硬编码为幻数感觉有点笨拙。但这绝对是我短期内要做的事情。谢谢! ?
    • 很高兴为您提供帮助。这种方法的好处是,以后,如果有其他方法可用,您可以轻松地重写修饰符 Papyrus,而无需更改代码。
    • 对于使用这种方法的任何人,您可能希望在 Apple 的人机界面指南中为每个尺寸等级参考 the official point sizes ?
    • 您可以使用 UIFontMetrics 来避免打开 sizeCategory。您需要编写一个从 Font.TextStyle 到 UIFont.TextStyle 的转换器,但这很简单。
    【解决方案2】:

    我偶然发现了一个通过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 修饰符已经允许我们这样做,并且可以通过为这个问题提供的替代方法之一来处理缩放。

    另外,请注意,由于样式应用在符合 ViewModifierstruct 中,而后者又响应环境的 sizeCategory 的更改,因此视图将在切换回时反映对可访问性设置的更改你的应用程序;所以没有必要重新启动它。

    【讨论】:

    • 这太棒了——谢谢!我更喜欢手动指定每种样式的比例。 ?
    • 这太棒了,我现在正在我的应用程序中使用它。小烦恼:它似乎不适用于具有 size 类环境修饰符的画布中的预览。
    【解决方案3】:

    如果您想保持类似 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)。参见Feedback 中的FB6523689。)

    【讨论】:

    • 是的,很好,这是一个很好的解决方法。我一直试图让我的新视图保持 100% SwiftUI,但对于某些事情,暂时使用 UIKit 可能是有意义的。我也对此提交了反馈(FB6203782),建议将fontStylefontFamily 一起移动到修饰符中,所以你可以这样做:Text("Hello, world!").fontStyle(.title).fontFamily("Avenir")
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-03-29
    • 2016-02-05
    • 1970-01-01
    • 2021-11-11
    • 2016-04-11
    • 2013-06-10
    相关资源
    最近更新 更多