Search code examples
iphoneswiftuiviewuigesturerecognizergesture

UIView pan gesture along Y axis


I have a UIView that I can drag and drop along the Y axis that snaps into 1 of 2 positions. The issue is that I have to drag it over a certain point in the Y axis in order for it to snap into place. This feels clunky when you try to quickly swipe but don't make it past that specific Y axis and it snaps back to the start position.

My desired goal is to have it snap into 1 of 2 positions depending on the direction the user was swiping when he released the UIView.

@objc func panGesture(recognizer: UIPanGestureRecognizer) {
    let window = UIApplication.shared.keyWindow
    let translation = recognizer.translation(in: self)
    let currentY = self.frame.minY
    let partialY = (window?.frame.height)!-194
    let halfWayPoint = ((window?.frame.height)!/2)-40

    self.frame = CGRect(x: 0, y: currentY + translation.y, width: self.frame.width, height: self.frame.height)
    recognizer.setTranslation(CGPoint.zero, in: self)

    switch recognizer.state {

    case .changed:

        // Prevent scrolling up past y:0
        if currentY <= 0
        {
            self.frame = CGRect(x: 0, y: 0, width: self.frame.width, height: self.frame.height)
        }

    case .ended:

        // Snap to 80 (y) (expanded)
        if currentY < 80 || currentY > 80 && currentY < halfWayPoint
        {
            UIView.animate(withDuration:0.62, delay: 0.0, usingSpringWithDamping: 0.62, initialSpringVelocity: 0.5, options: .curveEaseInOut, animations:
                {
                    recognizer.view!.frame = CGRect(x: 0, y: 80, width: self.frame.width, height: self.frame.height)
            }, completion: { (true) in
                self.isExpanded = true
            })
        }

        // Snap back to partial (y) (original position)
        if currentY > partialY || currentY < partialY && currentY > halfWayPoint
        {
            UIView.animate(withDuration:0.62, delay: 0.0, usingSpringWithDamping: 0.62, initialSpringVelocity: 0.5, options: .curveEaseInOut, animations:
                {
                    recognizer.view!.frame = CGRect(x: 0, y: partialY, width: self.frame.width, height: self.frame.height)
            }, completion: {(true) in
                self.isExpanded = false
            })
        }

    default:
        print("Default")
    }

As you can see halfWayPoint is the point in which the view must be over or under in order to determine the position it snaps into. This is ineffective and I would like to do it depending on the direction (up or down) the user is dragging the view.


Solution

  • I suggest you use UIPanGestureRecognizers velicoty(in:) method. It gives you the speed of the swipe (in points per second).

    If you just add it to the currentY (preferably scaled with some constant), you will get that flicky feeling, and gesture will feel more natural.

    Just replace let currentY = self.frame.minY with let currentY = self.frame.minY + recognizer.velocity(in: nil).y * someConstant and play with someConstant until you get the feeling you want.