You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI如何实现多个Text视图动态调整字号时保持大小一致

原因说明

这是SwiftUI的默认布局逻辑,不属于系统Bug。每个Text视图会独立计算自身内容适配可用空间所需的缩放比例,仅保证自身内容可以完整展示,不会主动和同容器内的其他Text做字号同步,因此文本内容变化后容易出现三个文本字号不一致的情况。

解决方法

方案1:iOS 16及以上版本

直接给整个VStack添加uniformTypeScaling()修饰符,强制容器内所有文本使用统一的缩放比例即可:

VStack(alignment: .leading) {
    Text(text)
    Spacer(minLength: 20)
    Text(text)
    Spacer(minLength: 20)
    Text(text)
    Spacer(minLength: 20)
}
.lineLimit(7)
.font(.system(size: 100))
.minimumScaleFactor(0.01)
// 新增下面这行
.uniformTypeScaling()

方案2:兼容iOS 15及更早版本

通过PreferenceKey监听所有文本的实际适配字号,取最小值统一应用到所有文本上即可:

  1. 先定义用于传递最小字号的PreferenceKey
struct MinFontSizePreferenceKey: PreferenceKey {
    static var defaultValue: CGFloat = 100
    static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
        value = min(value, nextValue())
    }
}
  1. 改造原有页面代码,同步字号
struct ContentView: View {
    @State private var text = "Tap the screen!"
    @State private var syncFontSize: CGFloat = 100
    private let maxFontSize: CGFloat = 100
    private let minScaleFactor: CGFloat = 0.01
    
    var body: some View {
        HStack {
            VStack(alignment: .leading) {
                Text(text)
                    .background(
                        GeometryReader { proxy in
                            // 计算当前Text适配后的实际字号
                            let availableWidth = proxy.size.width
                            let actualFontSize = calculateFitFontSize(for: text, width: availableWidth, maxSize: maxFontSize)
                            Color.clear.preference(key: MinFontSizePreferenceKey.self, value: actualFontSize)
                        }
                    )
                    .font(.system(size: syncFontSize))
                Spacer(minLength: 20)
                Text(text)
                    .background(
                        GeometryReader { proxy in
                            let availableWidth = proxy.size.width
                            let actualFontSize = calculateFitFontSize(for: text, width: availableWidth, maxSize: maxFontSize)
                            Color.clear.preference(key: MinFontSizePreferenceKey.self, value: actualFontSize)
                        }
                    )
                    .font(.system(size: syncFontSize))
                Spacer(minLength: 20)
                Text(text)
                    .background(
                        GeometryReader { proxy in
                            let availableWidth = proxy.size.width
                            let actualFontSize = calculateFitFontSize(for: text, width: availableWidth, maxSize: maxFontSize)
                            Color.clear.preference(key: MinFontSizePreferenceKey.self, value: actualFontSize)
                        }
                    )
                    .font(.system(size: syncFontSize))
                Spacer(minLength: 20)
            }
            .lineLimit(7)
            .minimumScaleFactor(minScaleFactor)
            .onPreferenceChange(MinFontSizePreferenceKey.self) { minSize in
                syncFontSize = max(minSize, maxFontSize * minScaleFactor)
            }
            
            Spacer(minLength: 0)
        }
        .padding()
        .contentShape(Rectangle())
        .frame(width: 1600/2.5, height: 900/2.5)
        .onTapGesture {
            generateNewText()
        }
    }
    
    // 计算文本适配的最大字号
    private func calculateFitFontSize(for text: String, width: CGFloat, maxSize: CGFloat) -> CGFloat {
        var fontSize = maxSize
        let font = UIFont.systemFont(ofSize: fontSize)
        let constraintSize = CGSize(width: width, height: CGFloat.greatestFiniteMagnitude)
        while fontSize > 1 {
            let textSize = (text as NSString).boundingRect(
                with: constraintSize,
                options: .usesLineFragmentOrigin,
                attributes: [.font: UIFont.systemFont(ofSize: fontSize)],
                context: nil
            )
            if textSize.height <= 7 * font.lineHeight { // 7为你设置的lineLimit
                return fontSize
            }
            fontSize -= 1
        }
        return 1
    }
    
    private func generateNewText() {
        var verse = ""
        for _ in 0..<Int.random(in: 20...50) {
            verse+="example "
        }
        text = verse
    }
}

临时快速修复

如果暂时不想大改代码,可以给所有Text添加相同的layoutPriority(1)修饰符,强制布局系统给三个文本分配相同的布局优先级,大部分场景下可以缓解字号不一致的问题:

Text(text)
    .layoutPriority(1)

内容的提问来源于stack exchange,提问作者Denny L.

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.28 20:06:03