Skip to content

Instantly share code, notes, and snippets.

@Codelaby
Created April 20, 2025 18:13
Show Gist options
  • Save Codelaby/c99b9d6d3942792226405f17e9baec32 to your computer and use it in GitHub Desktop.
Save Codelaby/c99b9d6d3942792226405f17e9baec32 to your computer and use it in GitHub Desktop.
Play/pause loop animation swiftui
import SwiftUI
struct Cancel: CustomAnimation {
func animate<V: VectorArithmetic>(
value: V, time: TimeInterval, context: inout AnimationContext<V>
) -> V? {
return nil // Fin inmediato
}
func shouldMerge<V>(
previous: Animation, value: V, time: TimeInterval, context: inout AnimationContext<V>
) -> Bool where V : VectorArithmetic {
return true
}
}
struct AnimatableNumberModifier: Animatable, ViewModifier {
var animatableData: Double
@Binding var currentAngle: Double
init(number: Double, currentAngle: Binding<Double>) {
self.animatableData = number
self._currentAngle = currentAngle
}
func body(content: Content) -> some View {
content
.rotationEffect(.degrees(animatableData))
.onChange(of: animatableData) { _, newValue in
currentAngle = newValue.truncatingRemainder(dividingBy: 360)
}
}
}
extension View {
func numericAnimation(for number: Double, currentAngle: Binding<Double>) -> some View {
modifier(AnimatableNumberModifier(number: number, currentAngle: currentAngle))
}
}
struct TapableVinylView2: View {
enum SpinningStatus {
case spinning
case paused
case stopped
}
@State private var spinningStatus: SpinningStatus = .stopped
@State private var angle: Double = 0.0
@State private var currentAngle: Double = 0.0
var body: some View {
VStack {
Image("rose_apt_vinyl")
.resizable()
.frame(width: 256, height: 256)
.numericAnimation(for: angle, currentAngle: $currentAngle)
.onTapGesture {
handleTap()
}
.onLongPressGesture {
handleLongPress()
}
HStack {
Button("Play") {
restart()
}
.disabled(spinningStatus == .spinning)
Button("Pause") {
pause()
}
.disabled(spinningStatus != .spinning)
Button("Stop") {
stop()
}
.disabled(spinningStatus != .paused)
}
.padding()
}
}
private func handleTap() {
switch spinningStatus {
case .spinning:
pause()
case .paused:
restart()
case .stopped:
restart()
}
}
private func handleLongPress() {
stop()
}
private func restart() {
guard spinningStatus != .spinning else { return }
spinningStatus = .spinning
updateSpinning()
}
private func pause() {
spinningStatus = .paused
updateSpinning()
angle = currentAngle
}
private func stop() {
spinningStatus = .stopped
updateSpinning()
angle = currentAngle
withAnimation(.smooth) {
angle = angle - currentAngle
}
}
private func updateSpinning() {
withAnimation(spinningStatus == .spinning
? .linear(duration: 3).repeatForever(autoreverses: false)
: .init(Cancel())) {
angle += 360
}
}
}
#Preview {
TapableVinylView2()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment