Skip to content

Instantly share code, notes, and snippets.

@kieranb662
Last active March 29, 2022 22:37
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kieranb662/3401c00a9cf32794e62cc89b56d65bd7 to your computer and use it in GitHub Desktop.
Save kieranb662/3401c00a9cf32794e62cc89b56d65bd7 to your computer and use it in GitHub Desktop.
[Drag Gesture Implementations] #SwiftUI

Drag Gesture Implementations

Simple Example 1

Here I used @State CGSize values to represent both the viewState and the dragState

struct Example1: View {
    @State var viewState: CGSize = .zero
    @State var dragState: CGSize = .zero
    
    var body: some View {
        let dragGesture = DragGesture()
            .onChanged { (value) in
                self.dragState = value.translation
        }.onEnded { (value) in
            self.viewState.width += value.translation.width
            self.viewState.height += value.translation.height
            self.dragState = .zero
        }
        return Circle().foregroundColor(.blue).frame(width: 20, height: 20)
            .offset(x: dragState.width + viewState.width,
                    y: dragState.height + viewState.height)
            .gesture(dragGesture)
        
    }
}

Simple Example 2

This time I made the structure similar to the Composing SwiftUI Gestures article but without the long press gesture.

struct Example2: View {
    @State var viewState: CGSize = .zero
    @GestureState var dragState: DragState = .inactive
    
    enum DragState {
        case inactive
        case active(translation: CGSize)
        
        var translation: CGSize {
            switch self {
            case .active(translation: let translation):
                return translation
            default:
                return .zero
            }
        }
    }
    
    var body: some View {
        let dragGesture = DragGesture()
            .updating($dragState) { (value, state, _) in
                state = .active(translation: value.translation)
        }.onEnded { (value) in
            self.viewState.width += value.translation.width
            self.viewState.height += value.translation.height
        }
        
        return Circle().foregroundColor(.red).frame(width: 20, height: 20)
            .offset(x: dragState.translation.width + viewState.width,
                    y: dragState.translation.height + viewState.height)
            .gesture(dragGesture)
    }
}

Accessing Drag Values

The DragGesture.Value struct provides access to a bunch of data about the current drag.

  • time - The time associated with the current event.
  • location - The location of the current event.
  • startLocation - The location of the first event.
  • translation - The total translation fram the first event to the current event
  • predictedEndLocation - A prediction of where the final location would be if dragging stopped now, based on the current drag velocity.
  • predictedEndTranslation - A prediction of what the final translation would be if dragging stopped now, based on the current drag velocity.

Reference

Full Drag State

enum FullDragState {
    case inactive
    case active(
        time: Date,
        location: CGPoint,
        startLocation: CGPoint,
        translation: CGSize,
        predictedEndLocation: CGPoint,
        predictedEndTranslation: CGSize)
    
    
    var time: Date? {
        switch self {
        case .active(let time, _, _, _, _, _):
            return time
        default:
            return nil
        }
    }
    
    var location: CGPoint {
        switch self {
        case .active(_, let location, _, _, _, _):
            return location
        default:
            return .zero
        }
    }
    
    var startLocation: CGPoint {
        switch self {
        case .active(_, _, let startLocation, _, _, _):
            return startLocation
        default:
            return .zero
        }
    }
    
    var translation: CGSize {
        switch self {
        case .active(_, _, _, let translation, _, _):
            return translation
        default:
            return .zero
        }
    }
    
    var predictedEndLocation: CGPoint {
        switch self {
        case .active(_, _, _, _, let predictedEndLocation, _):
            return predictedEndLocation
        default:
            return .zero
        }
    }
    
    
    var predictedEndTranslation: CGSize {
        switch self {
        case .active(_, _, _, _, _, let predictedEndTranslation):
            return predictedEndTranslation
        default:
            return .zero
        }
    }
    
    var isActive: Bool {
        switch self {
        case .active(_, _, _, _, _, _):
            return true
        default:
            return false
        }
    }
}

Drag Values

struct DragValues: Equatable {
        var time: Date?
        var location: CGPoint
        var startLocation: CGPoint
        var translation: CGSize
        var predictedEndLocation: CGPoint
        var predictedEndTranslation: CGSize
        
        static func zero() -> DragValues{
            return DragValues(time: nil,
                              location: .zero,
                              startLocation: .zero,
                              translation: .zero,
                              predictedEndLocation: .zero,
                              predictedEndTranslation: .zero)
        }
        
    }

Drag Velocity Example

enum DragVelocity {
    case inactive
    case active(time: Date,
        location: CGPoint,
        startingLocation: CGPoint,
        translation: CGSize,
        velocity: CGSize)
    
    var time: Date? {
        switch self {
        case .active(let time, _, _, _, _):
            return time
        default:
            return nil
        }
    }
    
    var location: CGPoint {
        switch self {
        case .active(_, let location, _, _, _):
            return location
        default:
            return .zero
        }
    }
    
    var startLocation: CGPoint {
        switch self {
        case .active(_, _, let startLocation, _, _):
            return startLocation
        default:
            return .zero
        }
    }
    
    var translation: CGSize {
        switch self {
        case .active(_, _, _, let translation, _):
            return translation
        default:
            return .zero
        }
    }
    
    var velocity: CGSize {
        switch self {
        case .active(_, _, _, _,  let velocity):
            return velocity
        default:
            return .zero
        }
    }
    
    
    var isActive: Bool {
        switch self {
        case .active(_, _, _, _, _):
            return true
        default:
            return false
        }
    }
}

struct DragVelocityExample: View {
    @GestureState var dragState: DragVelocity = .inactive
    @State var viewState: DragValues = .zero()
    
    struct DragValues: Equatable {
        var time: Date?
        var location: CGPoint
        var startLocation: CGPoint
        var translation: CGSize
        var velocity: CGSize
        
        static func zero() -> DragValues{
            return DragValues(time: nil,
                              location: .zero,
                              startLocation: .zero,
                              translation: .zero,
                              velocity: .zero)
        }
        
    }
    
    
    
    var body: some View {
        
        let dragGesture = DragGesture()
            .updating($dragState, body: { (value, state, transaction) in
                if state.time == nil {
                    state = .active(time: value.time,
                    location: value.location,
                    startingLocation: value.startLocation,
                    translation: value.translation,
                    velocity: .zero)
                } else {
                    let vX = (value.location.x-state.location.x)/CGFloat((state.time?.timeIntervalSince(value.time) ?? 1))
                    let vY = (value.location.y-state.location.y)/CGFloat((state.time?.timeIntervalSince(value.time) ?? 1))
                    state = .active(time: value.time,
                                       location: value.location,
                                       startingLocation: value.startLocation,
                                       translation: value.translation,
                                       velocity: CGSize(width: vX, height: vY))
                    
                }
                
                
            })
            .onEnded { (value) in
                self.viewState = DragValues(time: value.time,
                                            location: value.location,
                                            startLocation: value.startLocation,
                                            translation: CGSize(width: value.translation.width + self.viewState.translation.width,
                                                                height: value.translation.height + self.viewState.translation.height),
                                            velocity: .zero)
                
                
                
                
        }
        
        return ZStack {
            Circle().foregroundColor( dragState.velocity.width > 40 ? .red: .blue)
                .frame(width: 50 , height: 50 )
                .offset(x: dragState.translation.width + viewState.translation.width,
                        y: dragState.translation.height + viewState.translation.height)
                .animation(.linear)
                .gesture(dragGesture)
            
        }
    }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment