The simplest answer to your question is to override the default animatableData
[inherited by the Animatable
protocol] with values used to draw your View. Here's an example of how to do that:
var animatableData: Double {
get { return percent }
set { percent = newValue }
}
Here's an example for you. It:
- Draws a Ring on the parent View.
As the value of percent [which you hook up when you define
animatableData
] changes, the animation updates the view by drawing a line along the circumference of the defined circle using the percent value at the time of the update.
import SwiftUI
/// This repeats an animation until 5 seconds elapse
struct SimpleAnswer: View {
/// the start/stop sentinel
static var shouldAnimate = true
/// the percentage of the circumference (arc) to draw
@State var percent = 0.0
/// animation duration/delay values
var animationDuration: Double { return 1.0 }
var animationDelay: Double { return 0.2 }
var exitAnimationDuration: Double { return 0.3 }
var finalAnimationDuration: Double { return 1.0 }
var minAnimationInterval: Double { return 0.1 }
var body: some View {
ZStack {
AnimatingOverlay(percent: percent)
.stroke(Color.yellow, lineWidth: 8.0)
.rotationEffect(.degrees(-90))
.aspectRatio(1, contentMode: .fit)
.padding(20)
.onAppear() {
self.performAnimations()
}
.frame(width: 150, height: 150,
alignment: .center)
Spacer()
}
.background(Color.blue)
.edgesIgnoringSafeArea(.all)
}
func performAnimations() {
run()
if SimpleAnswer.shouldAnimate {
restartAnimation()
}
/// Stop the Animation after 5 seconds
DispatchQueue.main.asyncAfter(deadline: .now() + 5.0, execute: { SimpleAnswer.shouldAnimate = false })
}
func run() {
withAnimation(.easeIn(duration: animationDuration)) {
percent = 1
}
let deadline: DispatchTime = .now() + animationDuration + animationDelay
DispatchQueue.main.asyncAfter(deadline: deadline) {
withAnimation(.easeOut(duration: self.exitAnimationDuration)) {
}
withAnimation(.easeOut(duration: self.minAnimationInterval)) {
}
}
}
func restartAnimation() {
let deadline: DispatchTime = .now() + 2 * animationDuration + finalAnimationDuration
DispatchQueue.main.asyncAfter(deadline: deadline) {
self.percent = 0
self.performAnimations()
}
}
}
/// Draws a Ring on the parent View
/// By default, `Shape` returns the instance of `EmptyAnimatableData` struct as its animatableData.
/// All you have to do is replace this default `EmptyAnimatableData` with a different value.
/// As the value of percent changes, the animation updates the view
struct AnimatingOverlay: Shape {
var percent: Double
func path(in rect: CGRect) -> Path {
let end = percent * 360
var p = Path()
p.addArc(center: CGPoint(x: rect.size.width/2, y: rect.size.width/2),
radius: rect.size.width/2,
startAngle: Angle(degrees: 0),
endAngle: Angle(degrees: end),
clockwise: false)
return p
}
/// This example defines `percent` as the value to animate by
/// overriding the value of `animatableData`
/// inherited as Animatable.animatableData
var animatableData: Double {
get { return percent }
set { percent = newValue }
}
}
#if DEBUG
struct SimpleAnswer_Previews : PreviewProvider {
static var previews: some View {
SimpleAnswer()
}
}
#endif
I found these links to help me answer your question. You should find them useful as well.
Wenderlich - How to Create a Splash Screen With SwiftUI
Majid - The Magic of Animatable Values
Animations in SwiftUI - Majid