Apple has made it easier than ever this year to smoothly animate a view after performing a drag gesture, by automatically using the final velocity of the drag gesture as the initial velocity of the animation. Here is a simple example:
struct DraggableCircle: View {
@State private var offset: CGSize = .zero
var body: some View {
let gesture = DragGesture(minimumDistance: 0)
.onChanged { gesture in
offset = gesture.translation
}
.onEnded { _ in
withAnimation(.bouncy(duration: 2)) {
offset = .zero
}
}
Circle()
.frame(width: 200, height: 200)
.offset(offset)
.gesture(gesture)
}
}
What this code is lacking, though, is the ability to smoothly interrupt the circle's animation by starting a new drag gesture before the animation finishes. Instead, the circle will instantly finish the animation, jumping away from your finger:
This makes complete sense because we are setting offset
back to .zero
when the gesture ends, without doing anything to figure out where on the screen the circle was positioned when the animation was interrupted.
What is the most straight-forward way to make this work without any sudden jumps?
One possible approach is to position the circle using the position of the gesture rather than its translation, which always puts the circle where the gesture is happening:
struct DraggableCircle_v2: View {
@State private var position: CGPoint?
var body: some View {
let gesture = DragGesture(minimumDistance: 0)
.onChanged { gesture in
position = gesture.location
}
.onEnded { _ in
withAnimation(.bouncy(duration: 2)) {
position = nil
}
}
GeometryReader { geo in
let center = CGPoint(
x: geo.size.width / 2,
y: geo.size.height / 2
)
Circle()
.frame(width: 200, height: 200)
.position(position ?? center)
.gesture(gesture)
}
}
}
This will work for most use cases. However, this always puts the center of the circle where your finger is, not taking into account where on the circle the gesture started. In other words, we have not made any progress towards the goal of figuring out where the circle is positioned at the moment the animation is interrupted.
Can we interrupt our animation using a gesture and figure out where the circle is positioned at the time of the interruption?
Edit: Here's what I'm trying to achieve: