SwiftUI animates changes in state. When a value such as isExpanded changes inside withAnimation(.spring), every view that depends on it moves to its new layout using a spring.
In iOS 17 and later, the simplest way to describe a spring is with two values: duration, roughly how long it takes to settle, and bounce, how much it overshoots. A bounce of 0 gives smooth motion with no overshoot; higher values add bounce. Apple also provides presets: .smooth, .snappy and .bouncy.
Older code uses .spring(response:dampingFraction:). Response is similar to duration, and a damping fraction of 1 means no bounce while lower values add bounce. Both forms describe the same physical spring.
The big advantage of springs is interruption. If a user taps again or a drag gesture ends mid-animation, the new spring starts from the current position and velocity, so there is no jump. For drag gestures, pass the gesture's velocity when the user releases, so the element continues in the direction it was thrown.
If the same component also needs to run on the web or Android, keeping spring values as shared motion tokens helps. ExodeUI, which we make, has a SwiftUI runtime, so one designed component with its springs can play in SwiftUI and on other platforms.