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

SwiftUI ViewModifier动画异常:首次显示与最后消失无过渡

问题:图标显示/消失的动画异常

我写了一个ViewModifier,用来在内容右侧添加图标,打算通过把.clipShape()修饰器的偏移量从-50动画到0来实现图标显示效果。但现在的问题是,图标第一次显示时直接弹出,没有动画;最后一次消失的时候也是同样的情况。

原代码实现

ViewModifier 代码

extension View {
    func addRightIcon(icon: Image, show: Bool) -> some View {
        return modifier(RightIconModifier(icon: icon, show: show))
    }
}

struct RightIconModifier: ViewModifier {
    
    var icon: Image
    private var iconMask: Int = 0

    init(icon: Image, show: Bool) {
        self.icon = icon
        withAnimation(Animation.interpolatingSpring(stiffness: 170, damping: 15).delay(2.5)) {
            iconMask = show ? 0 : -50
        }
    }
    
    func body(content: Content) -> some View {
        ZStack {
            content
                .overlay(rightIcon)
        }
    }
    
    var rightIcon: some View {
        icon
            .font(.system(size: 25))
            .foregroundColor(.black)
            .frame(maxWidth: .infinity,
                   maxHeight: .infinity,
                   alignment: .trailing)
            .padding()
            .clipShape(Rectangle().offset(x: CGFloat(iconMask)))
    }
}

使用示例

TextField(placeholder, text: $text).addRightIcon(icon: Image(systemName: "checkmark"), show: isTextValid)

var isTextValid: Bool {
        if !text.isEmpty {
            let validation = NSPredicate(format: "SELF MATCHES %@", "[’a-zA-Z]{3,20}")
            let validated = validation.evaluate(with: text)
            return validated
        }
        return false
    }

问题原因

原代码的动画逻辑写在init方法里,而init只会在ViewModifier实例创建时执行一次。当视图首次加载时,如果show状态为true,iconMask直接被设置为0,没有触发动画;当show最后一次变为false时,可能因为视图即将被销毁,init不会再次执行,导致动画失效。另外,iconMask是普通属性,SwiftUI无法跟踪其变化,状态更新时无法触发视图重绘和动画。

修复方案

修改代码,让SwiftUI能跟踪状态变化,并在状态切换时正确触发动画:

extension View {
    func addRightIcon(icon: Image, show: Bool) -> some View {
        modifier(RightIconModifier(icon: icon, show: show))
    }
}

struct RightIconModifier: ViewModifier {
    let icon: Image
    let show: Bool
    @State private var iconMask: CGFloat = -50

    func body(content: Content) -> some View {
        ZStack {
            content
                .overlay(rightIcon)
        }
        // 监听show状态变化,触发动画
        .onChange(of: show) { newValue in
            withAnimation(Animation.interpolatingSpring(stiffness: 170, damping: 15).delay(0.25)) {
                iconMask = newValue ? 0 : -50
            }
        }
        // 初始状态为true时,触发首次显示动画
        .onAppear {
            if show {
                withAnimation(Animation.interpolatingSpring(stiffness: 170, damping: 15).delay(0.25)) {
                    iconMask = 0
                }
            }
        }
    }
    
    var rightIcon: some View {
        icon
            .font(.system(size: 25))
            .foregroundColor(.black)
            .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .trailing)
            .padding()
            .clipShape(Rectangle().offset(x: iconMask))
    }
}

修改说明

  1. 将iconMask改为@State属性,让SwiftUI能够监测其变化,从而触发视图重绘和动画
  2. 移除init中的动画逻辑,改用onChange(of: show)监听显示状态的切换,每次状态变化时执行动画
  3. 添加onAppear方法处理初始状态为true的场景,确保图标首次显示时有动画效果
  4. 调整延迟时间为0.25秒(原2.5秒过长,可根据需求自行修改)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.25 17:48:08