Skip to content

Instantly share code, notes, and snippets.

@HarshilShah
Created April 29, 2023 05:17
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save HarshilShah/3e8a5ac15c55d2b56aab5cd342ea2534 to your computer and use it in GitHub Desktop.
Save HarshilShah/3e8a5ac15c55d2b56aab5cd342ea2534 to your computer and use it in GitHub Desktop.
A SwiftUI view that performs a looping animation in whole cycles. When the animation is disabled, it drives the current loop to completion, and pauses only then
struct CycledAnimationTimelineView<Content: View>: View {
var duration: Double
var isAnimating: Bool
@ViewBuilder var content: (Double) -> Content
@State private var isActuallyAnimating = false
@State private var startDate: Date?
var body: some View {
TimelineView(.animation(paused: !isActuallyAnimating)) { context in
content(animationProgress(at: context.date))
}
.onChange(of: isAnimating) { isAnimating in
if isAnimating {
startAnimation()
}
}
.onAppear {
if isAnimating {
startAnimation()
}
}
}
func startAnimation() {
isActuallyAnimating = true
if startDate == nil {
startDate = .now
}
}
func animationProgress(at date: Date) -> Double {
guard isActuallyAnimating else { return 0 }
guard let startDate else {
DispatchQueue.main.async { startDate = date }
return 0
}
let progress = (date.timeIntervalSince(startDate) / duration).truncatingRemainder(dividingBy: 1)
if !isAnimating,
progress.isApproximatelyEqual(to: 0, absoluteTolerance: 0.05, norm: \.magnitude) {
DispatchQueue.main.async {
isActuallyAnimating = false
self.startDate = nil
}
return .zero
}
return progress
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment