为什么SwiftUI的Shimmering加载动画在NavigationView中会出现跳动
问题原因
- 旧版无绑定动画API的副作用:你使用的无指定绑定值的
animation(_:)是SwiftUI不再推荐的早期API,会响应视图所有状态变更,包括NavigationView自带的布局调整、转场动画触发的状态变化,导致闪烁动画被意外打断、重启,产生跳动效果。 - 动画启动时机冲突:NavigationView首次渲染子视图时自带过渡动画,此时
onAppear立即修改opacity启动闪烁动画,两个动画叠加就会出现跳动。 - 重复添加动画修饰符:在每个
oneRectangle上都添加了重复动画修饰符,同一个opacity状态变化被多个动画响应,会加重动画异常。
解决方案
按照下面的规则修改代码即可解决NavigationView下的动画跳动问题:
- 替换无绑定的
animationAPI为animation(_:value:),仅指定当opacity变化时才触发动画,避免响应NavigationView的无关状态变更。 - 将动画修饰符统一移到
ShimmeringCell的根视图,不要在每个子矩形上重复添加。 - 给
onAppear中的opacity修改添加少量延迟,等NavigationView的自带转场动画结束后再启动闪烁动画。 - 移除多余的
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
相关产品推荐
相关产品推荐

