Search code examples
iosuiviewuipangesturerecognizer

Using UIPanGestureRecognizer to swipe down UIView


I have an IBAction for a UIPanGestureRecognizer in my UIView, I am able to handle the gesture and recognise the state changes from began, cancelled and ended, as well as respond to those changes.

However when using the sender.location to handle the swipe down, the UIView actually moves up once the gesture has began, and then continues to move down. The experience is jarring and I am not sure what I am doing wrong. Does anybody have any ideas ?

func update(_ translation: CGPoint, origin: CGPoint) {

    let offSetY = translation.y

    cardView.frame.origin.y = offSetY

    let multiplier = 1 - (translation.y / 2000)
    self.view.alpha = multiplier
}


func cancel(_ origin: CGPoint) {

    let animator = UIViewPropertyAnimator(duration: 0.6, dampingRatio: 0.6) {
        self.visualEffectView.alpha = 1
        self.cardView.alpha = 1.0
        self.view.alpha = 1.0
        self.cardView.center = origin
    }
    animator.startAnimation()
}

func finish() {
    let animator = UIViewPropertyAnimator(duration: 0.9, dampingRatio: 0.9) {
        self.visualEffectView.effect = nil
        self.dismiss(animated: true, completion: nil)
    }
    animator.startAnimation()
}



@IBAction func panGestureAction(_ sender: UIPanGestureRecognizer) {

    self.view.backgroundColor = .white
    let originalCardPosition = cardView.center

   //let cardOriginY = cardView.frame.origin.y
    let translation = sender.translation(in: self.cardView)
    let origin = sender.location(in: self.cardView)

        switch sender.state {
        case .changed:


            if translation.y > 0 { update(translation, origin: origin) }

        case .ended:

            let translation = sender.translation(in: self.cardView)

            if translation.y > 100 {

                finish()

            } else {
                cardView.alpha = 1.0
                cancel(originalCardPosition)

            }
        case .cancelled:
            cancel(originalCardPosition)

        default: break
        }

}

Solution

  • The problem is that you set the origin.y of the cardView directly to the value of translation.y. You need to add translation.y to the view's original y value determined when the gesture begins.

    Add a property to the class:

    var originalY: CGFloat = 0
    

    Then in the .began state of the gesture, set it:

    originalY = cardView.frame.origin.y
    

    Then in your update method you set the origin:

    cardView.frame.origin.y = originalY + offSetY