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

SwiftUI骨架动画异常上下弹跳,需仅实现淡入淡出效果

SwiftUI骨架视图弹跳问题解决

问题

开发SwiftUI骨架加载视图时,期望骨架在内容加载完成前仅循环执行淡入淡出动画,但实际运行时,骨架视图在淡入淡出的同时会出现上下弹跳。移除代码中的Spacer()能减少垂直动画幅度,但无法彻底解决问题。

原因

当前的动画被应用在整个Rectangle视图上,SwiftUI默认动画会影响所有可动画属性(包括布局相关属性)。再结合VStack和Spacer的布局特性,动画触发时视图布局会被重新计算,从而产生弹跳效果。核心问题是没有将动画仅限制在透明度变化上,导致布局属性被意外带动。

解决方案

  1. 精准控制动画作用范围:只对opacity属性应用动画,避免影响布局
  2. 统一骨架视图与加载完成后内容的布局参数,消除布局切换时的波动

修改后的FadingSkeletonView

struct FadingSkeletonView: View {
    @State private var opacity: Double = 1.0
    
    var body: some View {
        Rectangle()
            .foregroundColor(.gray)
            .cornerRadius(10)
            .opacity(opacity)
            .onAppear {
                withAnimation(.easeInOut(duration: 0.5).repeatForever(autoreverses: true)) {
                    opacity = 0.3
                }
            }
    }
}

调整ProjectDashboardView布局

确保骨架视图和加载完成后的视图拥有完全一致的padding、frame等布局属性,同时替换Spacer()避免布局波动:

import SwiftUI

struct FadingSkeletonView: View {
    @State private var opacity: Double = 1.0
    
    var body: some View {
        Rectangle()
            .foregroundColor(.gray)
            .cornerRadius(10)
            .opacity(opacity)
            .onAppear {
                withAnimation(.easeInOut(duration: 0.5).repeatForever(autoreverses: true)) {
                    opacity = 0.3
                }
            }
    }
}

struct ProjectDashboardView: View {
    let project: Project?
    
    var body: some View {
        VStack(spacing: 0) { // 明确spacing消除默认布局差异
            if let unwrappedProject = project {
                if let url = unwrappedProject.imageUrl {
                    AsyncImage(url: url) { image in
                        image.resizable()
                    } placeholder: {
                        ProgressView()
                    }
                    .aspectRatio(contentMode: .fill)
                    .frame(height: 250)
                    .cornerRadius(10)
                    .clipped()
                    .padding()
                } else {
                    Rectangle()
                        .foregroundColor(.gray)
                        .frame(height: 250)
                        .cornerRadius(10)
                        .padding()
                }
                
                Text(unwrappedProject.title)
                    .font(.largeTitle)
                    .fontWeight(.bold)
                    .padding([.leading, .trailing]) // 和骨架视图保持一致的水平padding
            } else {
                FadingSkeletonView()
                    .frame(height: 250)
                    .padding()
                
                FadingSkeletonView()
                    .frame(height: 40)
                    .padding([.leading, .trailing])
            }
        }
        .frame(maxHeight: .infinity, alignment: .top) // 替代Spacer实现内容置顶,避免布局波动
        .padding()
    }
}

说明

  • 用withAnimation直接控制opacity值变化,确保动画仅作用于透明度,完全隔离布局属性
  • 统一加载前后视图的padding参数,设置VStack(spacing: 0)消除布局差异
  • 用.frame(maxHeight: .infinity, alignment: .top)替代Spacer(),既保留内容置顶效果,又避免Spacer带来的布局动画干扰

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 20:18:26