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 } } }
代码说明
AnimatedFontView封装了UIKit的UILabel,使用UIKit的线性动画执行字体大小变化,确保动画行为与原SwiftUI动画一致。- 通过
CADisplayLink监听屏幕刷新的每一刻,实时获取UILabel当前的字体大小,并更新绑定的currentFontSize变量。 - 主视图中点击时切换
targetFontSize触发动画,按下空格键即可打印当前的实时字体大小。
内容的提问来源于stack exchange,提问作者Roberto Arista
相关产品推荐
相关产品推荐

