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

SwiftUI中捕获动画过程中fontSize变量实时值的方法

获取SwiftUI动画过程中字体大小的实时值

问题场景

在macOS SwiftUI视图中,点击触发字体从50.0到200.0的3秒线性动画,绑定空格键的隐藏按钮需要打印动画过程中的实时字体大小,但直接访问@State变量只能得到动画结束后的最终值。原始代码如下:

import SwiftUI

struct MeasureFontSizeView: View {
    
    @State private var fontSize = 50.0
    
    var body: some View {
        ZStack {
            Text("Hello!")
                .font(.custom("Georgia", size: fontSize))
                .frame(maxWidth: .infinity, maxHeight: .infinity)
            
            Button {
                print(fontSize)
            } label: {
                Text("")
            }
            .buttonStyle(.borderless)
            .keyboardShortcut(" ", modifiers: [])
        }
        
        .onTapGesture {
            withAnimation(.linear(duration: 3)) {
                fontSize = 200.0
            }
        }
    }
}

问题原因

SwiftUI的@State变量在withAnimation块中修改时,变量本身会直接跳转到目标值,动画效果是由SwiftUI渲染系统在UI层进行插值计算实现的,并非实时更新@State变量的值。因此直接打印fontSize只能获取最终的目标值,无法拿到动画过程中的中间值。

解决方案

通过结合UIKit的动画系统和CADisplayLink,可以实时捕获动画过程中的字体大小变化。我们可以创建一个UIViewRepresentable包装视图,用UIKit执行动画,并通过CADisplayLink监听每帧的字体大小,将实时值绑定到SwiftUI的状态变量中。

修改后的完整代码:

import SwiftUI
import UIKit

struct AnimatedFontView: UIViewRepresentable {
    let text: String
    let fontName: String
    @Binding var currentSize: CGFloat
    var targetSize: CGFloat
    var duration: Double
    
    func makeUIView(context: Context) -> UILabel {
        let label = UILabel()
        label.text = text
        label.font = UIFont(name: fontName, size: currentSize)
        label.textAlignment = .center
        return label
    }
    
    func updateUIView(_ uiView: UILabel, context: Context) {
        // 移除之前的displayLink,避免重复监听
        context.coordinator.displayLink?.invalidate()
        
        // 执行UIKit线性动画
        UIView.animate(withDuration: duration, delay: 0, options: .curveLinear) {
            uiView.font = UIFont(name: self.fontName, size: self.targetSize)
        }
        
        // 创建CADisplayLink监听每帧的字体大小
        let displayLink = CADisplayLink(target: context.coordinator, selector: #selector(Coordinator.updateCurrentSize))
        displayLink.add(to: .main, forMode: .common)
        context.coordinator.displayLink = displayLink
        context.coordinator.label = uiView
        context.coordinator.currentSize = $currentSize
    }
    
    func makeCoordinator() -> Coordinator {
        Coordinator()
    }
    
    class Coordinator: NSObject {
        var displayLink: CADisplayLink?
        weak var label: UILabel?
        var currentSize: Binding<CGFloat>?
        
        @objc func updateCurrentSize() {
            guard let label = label, let currentSizeBinding = currentSize else { return }
            currentSizeBinding.wrappedValue = label.font.pointSize
        }
    }
}

struct MeasureFontSizeView: View {
    @State private var targetFontSize = 50.0
    @State private var currentFontSize = 50.0
    
    var body: some View {
        ZStack {
            AnimatedFontView(
                text: "Hello!",
                fontName: "Georgia",
                currentSize: $currentFontSize,
                targetSize: targetFontSize,
                duration: 3
            )
            .frame(maxWidth: .infinity, maxHeight: .infinity)
            
            Button {
                print(currentFontSize)
            } label: {
                Text("")
            }
            .buttonStyle(.borderless)
            .keyboardShortcut(" ", modifiers: [])
        }
        .onTapGesture {
            targetFontSize = targetFontSize == 50.0 ? 200.0 : 50.0
        }
    }
}

代码说明

  1. AnimatedFontView封装了UIKit的UILabel,使用UIKit的线性动画执行字体大小变化,确保动画行为与原SwiftUI动画一致。
  2. 通过CADisplayLink监听屏幕刷新的每一刻,实时获取UILabel当前的字体大小,并更新绑定的currentFontSize变量。
  3. 主视图中点击时切换targetFontSize触发动画,按下空格键即可打印当前的实时字体大小。

内容的提问来源于stack exchange,提问作者Roberto Arista

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 04:35:00