// // ContentView.swift // WonkyDragAnimationDemo // // Created by Michelle Ellis on 2023-07-25. // import SwiftUI struct ContentView: View { // 💰💰💰💰💰 // The problem is the `.speed(2)` modifier on this animation. // If we omit the speed modifier, animations behave as expected. // With it, at the end of the animation, unpredictable things happen to the view's offset. // Good: // private static var defaultAnimation = { Animation.spring() }() // Bad: private static var defaultAnimation = { Animation.spring().speed(2) }() // Drag gesture stuff: // The offset from start of finger drag to current position. // Resets to (0,0) on end or cancel. @GestureState private var gestureDragOffset: CGSize = .zero // The *last* value received from onChanged. // We need this seperately from dragOffset because it has goodness like predictedEndLocation (which we use in our real app, not this demo) @State var lastDragUpdate: DragGesture.Value? @State var animation: Animation? = Self.defaultAnimation private static var defaultOffset: CGFloat = 100.0 @State private var offset: CGFloat = Self.defaultOffset var body: some View { ZStack(alignment: .bottom) { VStack(spacing: 0) { Text("Hello, wonky drag animation!") Spacer() } .background(.purple) .offset(y: offset) .animation(animation, value: self.offset) } .gesture( DragGesture() .updating($gestureDragOffset) { value, state, transaction in state = value.translation } .onChanged({ (value) in lastDragUpdate = value }) ) .onChange(of: self.gestureDragOffset) { _, newOffset in if let lastDragUpdate { if self.gestureDragOffset == .zero { self.lastDragUpdate = nil self.dragEnded() } else { self.dragChanged(lastDragUpdate) } } else { self.dragEnded() } } } // MARK: Drag Gesture func dragChanged(_ value: DragGesture.Value) { animation = nil offset = -1 * (value.startLocation.y - value.location.y - Self.defaultOffset) } func dragEnded() { offset = Self.defaultOffset animation = Self.defaultAnimation } }