Home > Enterprise >  Why does an animation in SwiftUI jump around
Why does an animation in SwiftUI jump around

Time:11-04

The goal is to have a Shimmering loading animation. Like facebook and many other apps use.

If I put this in a TabView it works as expected, but if I have the code just in a NavigationView it jump around like in the video. Why does this happen?

If someone could guide me to a solution that would be great!

Video: https://imgur.com/a/wZ3jI1f

Here is the code:

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
}

CodePudding user response:

You just need two small changes (see comments inlined in the code):

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
        }
    }

In terms of why: For the animation modifier, use of the animation without the value parameter is deprecated and we should expect to use value:. For the DispatchQueue, my best guess is that the NavigationView has an implicit animation attached and we need to start this animation on the next run loop to make sure that it's separate.

  • Related