简体   繁体   English

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

[英]Is it possible to use dynamic type sizes with a custom font in SwiftUI?

I'm playing around with SwiftUI, and want to use a custom UI font for my project.我正在使用 SwiftUI,并希望为我的项目使用自定义 UI 字体。 However, I don't want to lose the dynamic type resizing that comes with the built-in font classes (eg Large Title).但是,我不想丢失内置字体类(例如大标题)附带的动态类型调整大小。

Apple does provide a custom font modifier for Text : Apple 确实为Text提供了自定义字体修饰符:

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

However, this fixes the size to 17pt.但是,这会将大小固定为 17pt。 When you run this on a device or in the Simulator and open the Accessibility Inspector to adjust the OS-level font size, the Text element does not update.当您在设备上或模拟器中运行它并打开辅助功能检查器以调整操作系统级字体大小时, Text元素不会更新。

The size: parameter is not optional, so you must pass in something. size:参数不是可选的,所以你必须传入一些东西。 And unfortunately, you can't get the size of an existing font (even a custom one), because Font does not have a size parameter.不幸的是,你不能得到的size现有的字体(甚至是一个自定义),因为Font不具有size参数。

It seems to be a common pattern in the rest of SwiftUI that parameters can either be optional, or you can pass in nil to explicitly disable certain behavior.在 SwiftUI 的其余部分中,参数可以是可选的,或者您可以传入nil以显式禁用某些行为,这似乎是一种常见模式。 I would expect the size: parameter on .custom() to be optional, and internally either use the size from a previous Font modifier, or to use the default size set by Text .我希望.custom()上的size:参数是可选的,并且在内部使用来自先前Font修饰符的大小,或者使用Text设置的默认大小。

Alternately, the static methods that define system styles (eg .largeTitle ) could accept an argument that provides a custom font name: .largeTitle("Papyrus")或者,定义系统样式(例如.largeTitle )的静态方法可以接受提供自定义字体名称的参数: .largeTitle("Papyrus")

Does anyone have a workaround?有没有人有解决方法?

The way I would do it, is by creating a custom modifier that can be bound to the changes of the environment's size category:我这样做的方法是创建一个自定义修饰符,该修饰符可以绑定到环境大小类别的变化:

Whenever you need to use Papyrus, you would use it like this:每当你需要使用 Papyrus 时,你会像这样使用它:

Text("Hello World!").modifier(Papyrus())

or like this:或者像这样:

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))

Your text will now dynamically change without further work.您的文本现在将无需进一步工作即可动态更改。 This is the same code, reacting to different text size preference:这是相同的代码,对不同的文本大小首选项做出反应:

在此处输入图片说明

And your Papyrus() implementation will look something like this.你的 Papyrus() 实现看起来像这样。 You'll need to figure out the right values for each category, this is just an example:您需要为每个类别找出正确的值,这只是一个示例:

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

}

UPDATE更新

I modified the implementation to accept a text style as parameter.我修改了实现以接受文本样式作为参数。

I stumbled upon a nice way to achieve this also via ViewModifier .我也通过ViewModifier偶然发现了一个很好的方法来实现这ViewModifier I borrowed the base modifier from this Hacking With Swift's article on Dynamic Type and Custom Fonts.我从Hacking With Swift关于动态类型和自定义字体的这篇文章中借用了 base 修饰符。 Here's the result:结果如下:

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

And usage:和用法:

Text("Hello World!")
    .customFont(name: "Georgia", style: .headline, weight: .bold)

This way you can stick to bundled Text Styles without needing to provide sizes explicitly.通过这种方式,您可以坚持使用捆绑的文本样式,而无需明确提供大小。 Should you want to do so, the font modifier already allow us to, and the scaling could be handled through one of the alternative approaches given to this question.如果您想这样做, font修饰符已经允许我们这样做,并且可以通过针对此问题提供的替代方法之一来处理缩放。

Also, please note that because the styling is applied within a ViewModifier conformant struct , which in turn responds to changes to the environment's sizeCategory , the views will reflect changes to the accessibility settings right upon switching back to your app;另外,请注意,因为样式应用在符合ViewModifier struct ,该struct反过来响应环境sizeCategory的更改,因此在切换回您的应用程序时,视图将反映可访问性设置的更改; so there's no need to restart it.所以没有必要重新启动它。

If you'd like to keep a SwiftUI-like style, you can extend Font for UIKit-compatible platforms:如果你想保持类似 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
}

Then, to use it:然后,使用它:

Text("Hello World!")
    .font(.myHeadline)

Note that your custom fonts won't update unless you restart the application .请注意,除非您重新启动应用程序,否则您的自定义字体不会更新 This means that the canvas preview won't work either this way.这意味着画布预览不会以这种方式工作。

I'll be investigating this topic further as soon as I find time for it.一旦我找到时间,我将进一步研究这个主题。

(Also, this should be a native feature. If there's Font.system(_ style: Font.TextStyle, design: Font.Design = .default) , there should be Font.custom(_ name: String, style: Font.TextStyle) too. See FB6523689 in Feedback.) (此外,这应该是本机功能。如果有Font.system(_ style: Font.TextStyle, design: Font.Design = .default) ,则应该有Font.custom(_ name: String, style: Font.TextStyle)也是。请参阅反馈中的 FB6523689。)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM