Reputation: 9454
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?
Upvotes: 1
Views: 1600
Reputation: 9075
Here's a simple ViewModifier version, which isolatest the logic into a pulseEffect()
similiar to the native scaleEffect()
:
struct PulseEffect: ViewModifier {
@State private var pulseIsInMaxState: Bool = true
private let range: ClosedRange<Double>
private let duration: TimeInterval
init(range: ClosedRange<Double>, duration: TimeInterval) {
self.range = range
self.duration = duration
}
func body(content: Content) -> some View {
content
.opacity(pulseIsInMaxState ? range.upperBound : range.lowerBound)
.onAppear { pulseIsInMaxState = false }
.animation(.smooth(duration: duration).repeatForever(), value: pulseIsInMaxState)
}
}
public extension View {
func pulseEffect(range: ClosedRange<Double> = 0.5...1, duration: TimeInterval = 1) -> some View {
modifier(PulseEffect(range: range, duration: duration))
}
}
#Preview {
Text("Hello, world!")
.pulseEffect()
}
Upvotes: 1
Reputation: 30421
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()
}
}
}
Upvotes: 8