Infinite animations in SwiftUI
SwiftUI gives a powerful API to create custom animations, but it isn't obvious how to create animations that loop indefinitely.
First, create a green dot. An small implementation could be like the following:
struct OnlineIndicator: View {
var body: some View {
Circle()
.foregroundColor(.green)
}
}
To make the blinking effect it is possible to use the opacity of the view:
struct OnlineIndicator: View {
var body: some View {
Circle()
.opacity(0.2)
.foregroundColor(.green)
}
}
But this just gives a semi-transparent green dot. What it needs is to go from 0.2
to 1.0
first. In order to accomplish that add an opacity state variable that holds the value and onAppear
change it to 1.0
:
struct OnlineIndicator: View {
@State private var opacity: CGFloat = 0.2
var body: some View {
Circle()
.opacity(opacity)
.foregroundColor(.green)
.onAppear(perform: { opacity = 1 })
}
}
Now the green dot goes from 0.2
to 1.0
but there's no animation at all, so this happens instantaneously, making it look like it didn't happen. To show this transition smoothly and with animation, add the animation modifier associated with the value that's being modified to prevent weird animations.
struct OnlineIndicator: View {
@State private var opacity: CGFloat = 0.2
var body: some View {
Circle()
.opacity(opacity)
.foregroundColor(.green)
.animation(.easeInOut(duration: 1), value: opacity)
.onAppear(perform: { opacity = 1 })
}
}
This solution will animate just once and it'll stop. So it isn't finished yet. Thanks to SwiftUI's animation API, views can get an infinite animation just by adding .repeatForever()
to the animation.
struct OnlineIndicator: View {
@State private var opacity: CGFloat = 0.2
var body: some View {
Circle()
.opacity(opacity)
.foregroundColor(.green)
.animation(
.easeInOut(duration: 1).repeatForever(),
value: opacity
)
.onAppear(perform: { opacity = 1 })
}
}
With this, the green dot now breathes in and out. But still there are improvements to make, like abstracting the opacity animation logic as a view modifier:
/* BlinkAnimation.swift */
struct BlinkAnimation: ViewModifier {
@State private var opacity: CGFloat = 0.2
func body(content: Content) -> some View {
content
.opacity(opacity)
.animation(
.easeInOut(duration: 1).repeatForever(),
value: opacity
)
.onAppear(perform: { opacity = 1 })
}
}
extension View {
func blink() -> some View {
modifier(BlinkAnimation())
}
}
/* OnlineIndicator.swift */
struct OnlineIndicator: View {
var body: some View {
Circle()
.blink()
.foregroundColor(.green)
}
}