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

为什么SwiftUI的Shimmering加载动画在NavigationView中会出现跳动

问题原因
  • 旧版无绑定动画API的副作用:你使用的无指定绑定值的animation(_:)是SwiftUI不再推荐的早期API,会响应视图所有状态变更,包括NavigationView自带的布局调整、转场动画触发的状态变化,导致闪烁动画被意外打断、重启,产生跳动效果。
  • 动画启动时机冲突:NavigationView首次渲染子视图时自带过渡动画,此时onAppear立即修改opacity启动闪烁动画,两个动画叠加就会出现跳动。
  • 重复添加动画修饰符:在每个oneRectangle上都添加了重复动画修饰符,同一个opacity状态变化被多个动画响应,会加重动画异常。
解决方案

按照下面的规则修改代码即可解决NavigationView下的动画跳动问题:

  1. 替换无绑定的animationAPI为animation(_:value:),仅指定当opacity变化时才触发动画,避免响应NavigationView的无关状态变更。
  2. 将动画修饰符统一移到ShimmeringCell的根视图,不要在每个子矩形上重复添加。
  3. 给onAppear中的opacity修改添加少量延迟,等NavigationView的自带转场动画结束后再启动闪烁动画。
  4. 移除多余的transition修饰符,避免额外的动画干扰。

修改后的完整代码如下:

import SwiftUI

struct ShimmeringListView: View {
    public var body: some View {
        VStack {
            ShimmeringCell()
            ShimmeringCell()
        }
    }
}

private struct ShimmeringCell: View {
    @State private var opacity: Double = Constants.minOpacity
    // 统一动画定义
    private let repeatedAnimation = Animation.easeInOut(duration: Constants.duration).repeatForever(autoreverses: true)
    
    public var body: some View {
        cardShimmerAnimation
            // 统一添加绑定opacity的动画
            .animation(repeatedAnimation, value: opacity)
            .onAppear {
                // 加0.1秒延迟避开NavigationView的自带转场动画
                DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
                    self.opacity = Constants.maxOpacity
                }
            }
    }
    
    var cardShimmerAnimation: some View {
        VStack {
            ZStack {
                backgroundRectangle
                shimmerRectangles
            }
        }
        .padding(.bottom, 20)
        .padding(.top, 16)
    }
    
    var backgroundRectangle: some View {
        Rectangle()
            .fill(
                LinearGradient(
                    gradient: Gradient(stops: [
                        Gradient.Stop(color: .lightGray, location: 0.476),
                        Gradient.Stop(color: .white, location: 0.524)
                    ]),
                    startPoint: .top,
                    endPoint: .bottom))
            .frame(height: 269)
            .cornerRadius(Constants.cornerRadius)
            .padding(.horizontal, 15)
    }
    
    var shimmerRectangles: some View {
        VStack {
            oneRectangle
                .frame(height: 28)
                .padding(.trailing, 73)
                .padding(.bottom, 2)
            oneRectangle
                .frame(height: 20)
                .padding(.trailing, 188)
                .padding(.bottom, 2)
            oneRectangle
                .frame(height: 20)
                .padding(.trailing, 130)
                .padding(.bottom, 20)
            oneRectangle
                .frame(height: 20)
                .padding(.trailing, 188)
                .padding(.vertical, 8)
            oneRectangle
                .frame(height: 20)
                .padding(.trailing, 116)
            Divider()
                .padding(.horizontal, 32)
                .padding(.vertical, 8)
            oneRectangle
                .frame(height: 20)
                .padding(.trailing, 52)
        }
    }
    
    var oneRectangle: some View {
        RoundedRectangle(cornerRadius: Constants.cornerRadius)
            .fill(Color.loadingColor)
            .opacity(opacity)
            .padding(.leading, 31)
        // 移除了重复的动画、transition定义和onAppear逻辑
    }
}

private struct Constants {
    static let duration: Double = 0.9
    static let minOpacity: Double = 0.25
    static let maxOpacity: Double = 1.0
    static let cornerRadius: CGFloat = 6.0
}

// 如果你没有定义loadingColor可以保留这个扩展
extension Color {
    static let loadingColor = Color.gray.opacity(0.3)
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.27 21:06:03