Home > Software design >  SwiftUI view animates at unexpected path
SwiftUI view animates at unexpected path

Time:10-06

I made the following PulsatingView:

struct PulsatingView: View {
    
    @State private var opacity = 0.7
    @State private var scale: CGFloat = 0.5

    var body: some View {
        VStack {
            ZStack {
                Circle()
                    .fill(Color.black.opacity(opacity))
                    .animation(.easeInOut(duration: 2).delay(1.5).repeatForever(autoreverses: false))
                    .frame(width: 7, height: 7)
                    .scaleEffect(scale)
                    .animation(.easeIn(duration: 2.5).delay(1).repeatForever(autoreverses: false))
                Circle()
                    .fill(Color.black)
                    .frame(width: 7, height: 7)
            }
        }
        .onAppear {
            opacity = 0
            scale = 5
        }
    }
}

It produces the following result: (click to see animation; it's a .gif)

pulsating view screencast

However, when I add it to a view, inside a VStack for example, this happens: (click to see animation; it's a .gif)

pulsating view glitch

I have no idea why this happens or how I can fix it. I've already tried adding modifiers such as .fixedSize(horizontal: true, vertical: true) or .frame(width: 50, height: 50) to it but that doesn't help.

Does anyone know what's going on?

CodePudding user response:

Try using animation with joined value, like

Circle()
    .fill(Color.black.opacity(opacity))
    .animation(.easeInOut(duration: 2).delay(1.5).repeatForever(autoreverses: false), value: opacity) // << here !!
    .frame(width: 7, height: 7)
    .scaleEffect(scale)
    .animation(.easeIn(duration: 2.5).delay(1).repeatForever(autoreverses: false), value: scale)    // << here !!

Important: if it is started in NavigationView then animatable state should be activated postponed. The reason and investigation details was provided in https://stackoverflow.com/a/66643096/12299030.

CodePudding user response:

This issue is not about your code it is because NavigationView to fix it we should use DispatchQueue.main.async I wanted refactor your code as well, hope help you, also you can avoid hard coded value as much as possible, for example you can apply the frame in outside of view:

struct ContentView: View {
    
    var body: some View {
        
        CircleView(lineWidth: 0.5, color: .red, scale: 5.0)
            .frame(width: 7, height: 7)
        
    }
    
}




struct CircleView: View {

    let lineWidth: CGFloat
    let color: Color
    let scale: CGFloat
    
    @State private var startAnimation: Bool = Bool()
    
    var body: some View {
        
        Circle()
            .strokeBorder(style: .init(lineWidth: startAnimation ? .zero : lineWidth)).opacity(startAnimation ? .zero : 1.0)
            .scaleEffect(startAnimation ? scale : 1.0)
            .overlay( Circle() )
            .foregroundColor(color)
            .onAppear() { DispatchQueue.main.async { startAnimation.toggle() } }
            .animation(.easeIn(duration: 2.5).delay(1).repeatForever(autoreverses: false), value: startAnimation)
        
    }
}
  • Related