首页 > 解决方案 > 为什么 SwiftUI 中的动画会跳来跳去

问题描述

目标是有一个闪烁的加载动画。像 facebook 和许多其他应用程序一样使用。

如果我把它放在 TabView 中,它会按预期工作,但如果我在 NavigationView 中有代码,它会像在视频中一样跳来跳去。为什么会这样?

如果有人可以指导我找到一个很棒的解决方案!

视频: https ://imgur.com/a/wZ3jI1f

这是代码:

import SwiftUI

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

private struct ShimmeringCell: View {
    @State private var opacity: Double = Constants.minOpacity
    
    public var body: some View {
        cardShimmerAnimation
    }
    
    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 {
        let baseAnimation = Animation.easeInOut(duration: Constants.duration)
        let repeatedAnimation = baseAnimation.repeatForever(autoreverses: true)
        
        return RoundedRectangle(cornerRadius: Constants.cornerRadius)
            .fill(Color.loadingColor)
            .opacity(opacity)
            .transition(.opacity)
            .padding(.leading, 31)
            .animation(repeatedAnimation)
            .onAppear {
                self.opacity = Constants.maxOpacity
            }
    }
}

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
}

标签: iosswiftswiftui

解决方案


您只需要两个小的更改(请参阅代码中内联的注释):

return RoundedRectangle(cornerRadius: Constants.cornerRadius)
    .fill(Color.loadingColor)
    .opacity(opacity)
    .transition(.opacity)
    .padding(.leading, 31)
    .animation(repeatedAnimation, value: opacity) //<-- Add value: parameter
    .onAppear {
        DispatchQueue.main.async { //<-- wrap in DispatchQueue.main.async
            self.opacity = Constants.maxOpacity
        }
    }

至于为什么:对于修饰符animation,不推荐使用animation不带value参数的,我们应该期望使用value:. 对于DispatchQueue,我最好的猜测是NavigationView附加了一个隐式动画,我们需要在下一个运行循环中启动这个动画,以确保它是独立的。


推荐阅读