如何使用 SwiftUI 缩放文本以适应父视图?

How to scale text to fit parent view with SwiftUI?

我想在圆形视图中创建一个文本视图。字体大小应自动设置为适合圆圈的大小。如何在 SwiftUI 中完成此操作?我尝试了 scaledToFill 和 scaledToFit 修饰符,但它们对文本视图没有影响:

struct ContentView : View {
    var body: some View {
        ZStack {
            Circle().strokeBorder(Color.red, lineWidth: 30)
            Text("Text").scaledToFill()
        }
    }
}

要实现此目的,您不需要 ZStack。您可以为 Text:

添加背景
Text("Text text text?")
    .padding()
    .background(
       Circle()
          .strokeBorder(Color.red, lineWidth: 10)
          .scaledToFill()
          .foregroundColor(Color.white)
    )

结果是这样的:

一种可能 "hack" 是使用大字体和小比例因子,这样它会自行缩小:

ZStack {
    Circle().strokeBorder(Color.red, lineWidth: 30)

    Text("Text")
        .padding(40)
        .font(.system(size: 500))
        .minimumScaleFactor(0.01)
     }
}

可以使用 GeometryReader 使其在横屏模式下也能正常工作。

它首先检查宽度或高度是否较小,然后根据其中较小的调整字体大小。

GeometryReader{g in
    ZStack {
        Circle().strokeBorder(Color.red, lineWidth: 30)
        Text("Text")
            .font(.system(size: g.size.height > g.size.width ? g.size.width * 0.4: g.size.height * 0.4))
    }
}

这是一个将文本大小调整代码隐藏在自定义修饰符中的解决方案,它可以应用于任何视图,而不仅仅是一个圆,并采用一个参数来指定文本应占据的视图分数。

(我不得不同意,虽然@szemian 的解决方案仍然不理想,但由于其他方法固有的问题,她的方法似乎是我们可以使用当前 SwiftUI 实现的最佳方法。@simibac 的答案需要摆弄才能找到任何时候文本或其属性——字体、粗细等——被更改时替换 0.4 的新幻数,@giuseppe-sapienza 不允许指定圆的大小,只能指定字体大小正文。)

struct FitToWidth: ViewModifier {
    var fraction: CGFloat = 1.0
    func body(content: Content) -> some View {
        GeometryReader { g in
        content
            .font(.system(size: 1000))
            .minimumScaleFactor(0.005)
            .lineLimit(1)
            .frame(width: g.size.width*self.fraction)
        }
    }
}

使用修饰符,代码变成这样:

    var body: some View {
        Circle().strokeBorder(Color.red, lineWidth: 30)
            .aspectRatio(contentMode: .fit)
            .overlay(Text("Text")
                .modifier(FitToWidth(fraction: fraction)))
    }

此外,当 Xcode 的未来版本提供 SwiftUI 改进以消除 .minimumScaleFactor 黑客攻击时,您只需更新修饰符代码即可使用它。 :)

如果您想了解分数参数的工作原理,可以通过以下代码使用滑块以交互方式对其进行调整:

struct ContentView: View {

    @State var fraction: CGFloat = 0.5

    var body: some View {
        VStack {
            Spacer()
            Circle().strokeBorder(Color.red, lineWidth: 30)
                .aspectRatio(contentMode: .fit)
                .overlay(Text("Text")
                .modifier(FitToWidth(fraction: fraction)))
            Slider(value: $fraction, in:0.1...0.9, step: 0.1).padding()
            Text("Fraction: \(fraction, specifier: "%.1f")")
            Spacer()
        }
    }
}

这是它的样子:

您想允许您的文本:

  • 缩小到一定限度
  • 1(或几)行

您可以根据需要选择此比例因子限制。通常情况下,您不会缩小到超出可读范围或超出会使您的设计看起来很糟糕的限制

struct ContentView : View {
var body: some View {
    ZStack {
        Circle().strokeBorder(Color.red, lineWidth: 30)
        Text("Text")
            .scaledToFill()
            .minimumScaleFactor(0.5)
            .lineLimit(1)
    }
}

}

我混合使用了@Simibac 和@Anton 的答案,结果被 iOS 14.0 破坏了,所以这就是我修复它的方法。应该也适用于 SwiftUI 1.0。

struct FitSystemFont: ViewModifier {
    var lineLimit: Int
    var minimumScaleFactor: CGFloat
    var percentage: CGFloat

    func body(content: Content) -> some View {
        GeometryReader { geometry in
            content
                .font(.system(size: min(geometry.size.width, geometry.size.height) * percentage))
                .lineLimit(self.lineLimit)
                .minimumScaleFactor(self.minimumScaleFactor)
                .position(x: geometry.frame(in: .local).midX, y: geometry.frame(in: .local).midY)
        }
    }
}

如您所见,我使用几何代理的frame(in:)方法获取局部坐标space,然后使用.midX.midY将其正确居中,因为正确的居中是我在 iOS 14.

上遇到的问题

然后我在View上设置了一个扩展:

extension View {
    func fitSystemFont(lineLimit: Int = 1, minimumScaleFactor: CGFloat = 0.01, percentage: CGFloat = 1) -> ModifiedContent<Self, FitSystemFont> {
        return modifier(FitSystemFont(lineLimit: lineLimit, minimumScaleFactor: minimumScaleFactor, percentage: percentage))
    }
}

所以用法是这样的:

Text("Your text")
    .fitSystemFont()

基于@JaimeeAz 的回答。添加了指定最小字体的选项。

import SwiftUI

public struct FitSystemFont: ViewModifier {
    public var lineLimit: Int?
    public var fontSize: CGFloat?
    public var minimumScaleFactor: CGFloat
    public var percentage: CGFloat

    public func body(content: Content) -> some View {
        GeometryReader { geometry in
            content
                .font(.system(size: min(min(geometry.size.width, geometry.size.height) * percentage, fontSize ?? CGFloat.greatestFiniteMagnitude)))
                .lineLimit(self.lineLimit)
                .minimumScaleFactor(self.minimumScaleFactor)
                .position(x: geometry.frame(in: .local).midX, y: geometry.frame(in: .local).midY)
        }
    }
}

public extension View {
    func fitSystemFont(lineLimit: Int? = nil, fontSize: CGFloat? = nil, minimumScaleFactor: CGFloat = 0.01, percentage: CGFloat = 1) -> ModifiedContent<Self, FitSystemFont> {
        return modifier(FitSystemFont(lineLimit: lineLimit, fontSize: fontSize, minimumScaleFactor: minimumScaleFactor, percentage: percentage))
    }
}

我对定时器也有同样的问题。不幸的是,计时器每秒更改一次文本,因此文本一直在跳来跳去并按比例放大和缩小。 我的方法是弄清楚,可以显示的最长计时器是多少——在我的例子中是“-44:44:44”——50pt 的大小会导致 227.7pt 的大框架。 227 除以 50(我之前使用的磅值),每个磅值的宽度为 4.5(向下舍入)。 注意:使用 1pt 大小它给了我一个 5.3 磅的大框架 - 所以字体越大越接近没有框架的实际文本大小。

因为我一直在使用 GeometryReader,所以我可以简单地设置一个固定的文本大小,使用

Text("-44:44:44")
.font(.system(size: geometry.size.width / 4.5))

效果非常好,如果没有“-”或没有显示小时数,我的左右会有一些 space,但文本不会跳来跳去。

这可以使用不同的比例因子进行改进,具体取决于此处显示的数字数量 - 因此“-mm:ss”的另一个比例因子。 当显示或隐藏小时数时,这会导致“跳转”——但我的需要很少发生这种情况。

我有固定大小的按钮,这对我自动收缩长文本很有用。

Text("This is a long label that will be scaled to fit:")
    .lineLimit(1)
    .minimumScaleFactor(0.5)

Source: Apple