Search code examples
iosswiftuipangesturerecognizer

Problem related to pan gesture flickers view


I am trying to move the bottom view up/ down using a pan gesture. It worked but has an issue of flickering and therefore it's not a smooth transition.

Here is the code

@IBOutlet weak var bottomViewHeight: NSLayoutConstraint!
@IBOutlet weak var bottomView: UIView!    
var maxHeight: CGFloat = 297
var minHeight: CGFloat = 128    

let panGest = PanVerticalScrolling(target: self, action: #selector(panAction(sender:)))
bottomView.addGestureRecognizer(panGest)    

@objc func panAction(sender: UIPanGestureRecognizer) {
    if sender.state == .changed {
        let endPosition = sender.location(in: self.bottomView)

        let differenceHeight = maxHeight - endPosition.y

        if differenceHeight < maxHeight && differenceHeight > minHeight {
            bottomViewHeight.constant = differenceHeight
            self.bottomView.layoutIfNeeded()
        }
    }
}

Here is the gesture class

class PanVerticalScrolling : UIPanGestureRecognizer {
    override init(target: Any?, action: Selector?) {
        super.init(target: target, action: action)
    }    

    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
        super.touchesMoved(touches, with: event)
           if state == .began {
           let vel = velocity(in: self.view)
           if abs(vel.x) > abs(vel.y) {
                state = .cancelled
           }
        }
    }
 }

And here in the image, you can check the actual issue

enter image description here


Solution

  • The problem is that you are repeatedly getting the touch position in the bottom view ... and then changing bottom view size.

    Since the touch Y is relative to the height of the view, it's bouncing around.

    Try this...

    Add a class property:

    var initialY: CGFloat = -1.0
    

    and change your panAction() to this:

    @objc func panAction(sender: UIPanGestureRecognizer) {
        if sender.state == .changed {
    
            if initialY == -1.0 {
                initialY = sender.location(in: self.view).y
            }
    
            let endPositionY = sender.location(in: self.view).y - initialY
    
            let differenceHeight = maxHeight - endPositionY
    
            if differenceHeight < maxHeight && differenceHeight > minHeight {
                bottomViewHeight.constant = differenceHeight
                self.bottomView.layoutIfNeeded()
            }
        }
    }
    

    You'll need to reset initialY to -1 each time you "end" the drag / resize process.


    Edit

    A better way - maintains current state:

    // these will be used inside panAction()
    var initialY: CGFloat = -1.0
    var initialHeight: CGFloat = -1.0
    
    @objc func panAction(sender: UIPanGestureRecognizer) {
        if sender.state == .changed {
            let endPositionY = sender.location(in: self.view).y - initialY
    
            let differenceHeight = self.initialHeight - endPositionY
    
            if differenceHeight < maxHeight && differenceHeight > minHeight {
                bottomViewHeight.constant = differenceHeight
            }
        }
        if sender.state == .began {
            // reset position and height tracking properties
            self.initialY = sender.location(in: self.view).y
            self.initialHeight = bottomViewHeight.constant
        }
    }