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

SwiftUI字体抖动动画Bug问题求助

SwiftUI Chips选中时动画失效问题解决

问题现象

ChipsView在选中元素时,字体粗细与颜色的动画无法正常执行,但取消选中时动画表现正常。已尝试更换所有动画类型、切换至主线程等操作,均未解决。

问题演示动图

问题根源

SwiftUI默认无法对Font.Weight这类枚举类型的切换进行平滑动画——枚举类型不支持插值计算,系统无法生成中间过渡状态,导致选中时的动画直接跳变。而取消选中时的异常“正常”表现,是系统对反向状态切换的偶然兼容,并非真正解决了问题。

解决方案

将枚举类型的字体粗细转换为可动画的数值类型,通过数值插值实现平滑过渡。以下是两种可行的实现方式:

方法一:使用自定义AnimatableModifier

通过自定义动画修饰符,将Font.Weight的切换映射到可插值的Double数值:

struct AnimatedFontWeightModifier: AnimatableModifier {
    var weight: Double
    let selectedWeight: Font.Weight = .bold
    let unselectedWeight: Font.Weight = .light
    
    var animatableData: Double {
        get { weight }
        set { weight = newValue }
    }
    
    func body(content: Content) -> some View {
        content
            .font(.system(size: 56, weight: weight > 0.5 ? selectedWeight : unselectedWeight))
    }
}

struct ChipsView: View {
    let title: String
    @Binding var selectedString: String

    private var isSelected: Bool { selectedString == title }
    private let selectedColor = Color.red
    private let unselectedColor = Color.gray
    @State private var animationProgress: Double = 0
    
    var body: some View {
        Text(title)
            .foregroundColor(isSelected ? selectedColor : unselectedColor)
            .modifier(AnimatedFontWeightModifier(weight: animationProgress))
            .contentShape(Rectangle())
            .onTapGesture {
                withAnimation(.bouncy(duration: 0.3)) {
                    selectedString = isSelected ? "" : title
                    animationProgress = isSelected ? 0 : 1
                }
            }
            .onAppear {
                animationProgress = isSelected ? 1 : 0
            }
            .onChange(of: isSelected) { newValue in
                animationProgress = newValue ? 1 : 0
            }
    }
}

方法二:直接绑定数值型变量控制字体粗细

简化实现,用Double变量直接驱动字体粗细的切换:

struct ChipsView: View {
    let title: String
    @Binding var selectedString: String

    private var isSelected: Bool { selectedString == title }
    private let selectedColor = Color.red
    private let unselectedColor = Color.gray
    @State private var fontWeightProgress: Double = 0
    
    var body: some View {
        Text(title)
            .font(.system(size: 56))
            .fontWeight(fontWeightProgress > 0.5 ? .bold : .light)
            .foregroundColor(isSelected ? selectedColor : unselectedColor)
            .contentShape(Rectangle())
            .onTapGesture {
                withAnimation(.bouncy(duration: 0.3)) {
                    selectedString = isSelected ? "" : title
                    fontWeightProgress = isSelected ? 0 : 1
                }
            }
            .onAppear {
                fontWeightProgress = isSelected ? 1 : 0
            }
            .onChange(of: isSelected) {
                fontWeightProgress = $0 ? 1 : 0
            }
    }
}

效果说明

两种方法均通过数值插值让SwiftUI生成平滑的过渡状态,同时颜色动画也会随状态切换正常执行,解决选中时动画失效的问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 09:40:11