Search code examples
swiftanimationswiftui

Making text pulsate in SwiftUI


So I am trying to make some text fade in and out to give a pulsate type effect, this is the code I have now:

struct SignInView: View {
    @State private var opacity: Double = 0.0
    @State private var pulseDown: Bool = false
    
    var body: some View {
        VStack(alignment: .center) {
            Spacer()
            
            Button(action: {
                AppDelegate.signInWithGoogle()
            }, label: {
                Text("Sign In")
                    .foregroundColor(Color.green)
                    .opacity(opacity)
            })
            
            Spacer()
        }
        .padding()
        .onAppear {
            self.pulsateText()
        }
    }
    
    private func pulsateText() {
        DispatchQueue.init(label: "Pulse").asyncAfter(deadline: .now() + 0.01) {
            if self.pulseDown {
                self.opacity -= 0.02
            } else {
                self.opacity += 0.02
            }
            
            if self.opacity > 1 {
                self.pulseDown = true
            } else if self.opacity < 0.1 {
                self.pulseDown = false
            }
            
            self.pulsateText()
        }
    }
}

It does exactly what I want and looks good, but I can't help but feel that an infinite recursive loop is not the right way to be doing it. I suppose I could make an infinite while instead of the infinite recursion, though that still seem not ideal. Is there a better way to achieve this?


Solution

  • There is an easier more SwiftUI-like way. It works by using an Animation's repeatForever(autoreverses:) method:

    struct SignInView: View {
    
        @State private var visible = true
        
        var body: some View {
            VStack {
                Spacer()
                
                Button(action: {
                    print("Sign in with Google")
    //                AppDelegate.signInWithGoogle()
                }, label: {
                    Text("Sign In")
                        .foregroundColor(Color.green)
                        .opacity(visible ? 1 : 0)
                })
                
                Spacer()
            }
            .padding()
            .onAppear(perform: pulsateText)
        }
        
        private func pulsateText() {
            withAnimation(Animation.easeInOut.repeatForever(autoreverses: true)) {
                visible.toggle()
            }
        }
    }