Jumping Ball Animation in SwiftUI
Problem Build a SwiftUI animation in which a ball repeatedly jumps — moving up and down to simulate a bounce — demonstrating command of SwiftUI's state and animation APIs.
Requirements
- A
Circle()view whose vertical position animates between a low and a high point - The bounce repeats indefinitely without manual timer management
@State private var isJumping: Bool(or an offset value) as the single source of truth- Animation driven declaratively from state, not by imperatively mutating the view
Core design
- A
@Stateproperty tracks the ball's vertical offset (or a boolean the offset is derived from). Changing state is the entire animation trigger — SwiftUI diffs the view tree and interpolates the changed value. - Apply
.offset(y: isJumping ? -100 : 0)to theCircle(), then attach.animation(.easeInOut(duration: 0.5).repeatForever(autoreverses: true), value: isJumping). autoreverses: truegives the down-swing for free — the animation plays forward then backward, so only one state edge is needed rather than two alternating writes.- Kick it off in
.onAppear { isJumping = true }. The single state flip plusrepeatForeversustains the loop; no timer, no completion handler, no manual re-trigger. withAnimation { }is the alternative entry point when the change is driven by an event (a tap) rather than a value the view already observes.- Easing choice carries the physics:
.easeInOutreads as a soft float, while.easeOutup and.easeIndown better mimics gravity — the ball should decelerate at the apex and accelerate into the floor.
Discussion points
- Declarative vs. imperative: SwiftUI renders from state and interpolates between snapshots, whereas UIKit typically drives frames through
CADisplayLink/timers orUIView.animateblocks. The SwiftUI version has no frame loop to leak or invalidate. .animation(value:)vs. the deprecated implicit.animation(_:)— the value-scoped form only animates that specific change, avoiding accidental animation of unrelated view updates.repeatForevernever completes, so any completion handler never fires — worth knowing before relying on one.- Trade-off:
.offsetmoves the rendered view without affecting layout (cheap, neighbors don't shift); animating frame or padding re-runs layout and can drag surrounding views along. - Realism extension: squash-and-stretch via
.scaleEffectat contact, and a shadow that shrinks as the ball rises, both driven off the same state. - For physics rather than easing curves,
.spring()or aTimelineViewwith a gravity computation gives genuine acceleration instead of an interpolated approximation.
asked …