Search code examples
iosswiftuiviewuigesturerecognizeruipangesturerecognizer

Swift how to use property animator with pan gesture


So I have a UIView with a PanGestureRecognizer attached that allows me to move the view up and down. As I move the view up, I want to decrease my button's alpha and when I move the view down I want to increase it back to normal.

I have tried to animate the button separately when it snaps into a position but this then breaks my PropertyAnimator.

My current implementation works find, provided that my finger is on the view as this returns the .changed state from the recogniser. But I also have implementation that snaps the view into position when the view is release at a certain point along the y axis. The problem is that it doesn't hit the .changed state and therefore change my hideAnimator.fractionComplete leaving the button half way through its animation.

Initialise property animator method

func initPropertyAnimator() {

    hideAnimator = UIViewPropertyAnimator(duration: 0.18, curve: .easeInOut, animations: {
        self.requestButton.alpha = 0
    })
}

Recogniser states in panGesture(recognizer: UIPanGestureRecognizer)

let currentY = self.frame.minY

switch recognizer.state {
    case .began:
        print("Began")

    case .changed:

        // Property animator
        let offset: CGFloat = window!.frame.height-194
        let percentage: CGFloat = (offset-currentY)/100
        print(percentage)
        hideAnimator?.fractionComplete = CGFloat(percentage)

    case .ended:
        // Snap back below 80 (y)
        if currentY < 80 || currentY > 80 && currentY < halfWayPoint
        {
            self.animator?.stopAnimation(true)
            UIView.animate(withDuration:0.62, delay: 0.0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0.5, options: .curveEaseInOut, animations:
                {
                    recognizer.view!.frame = CGRect(x: 0, y: 80, width: self.frame.width, height: self.frame.height)
                    self.requestButton.alpha = 0
                    self.houseView.frame = CGRect(x: 16, y: 87, width: self.boxWidth, height: 68)
                    self.officeView.frame = CGRect(x: 16, y: 171, width: self.boxWidth, height: 68)
                    self.carView.frame = CGRect(x: 16, y: 255, width: self.boxWidth, height: 68)
                    self.gardenView.frame = CGRect(x: 16, y: 339, width: self.boxWidth, height: 68)
            }, completion: { (true) in

            })
        }

        // Snap back above partial (y)
        if currentY > partialY || currentY < partialY && currentY > halfWayPoint
        {
            self.animator?.stopAnimation(true)
            UIView.animate(withDuration:0.62, delay: 0.0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0.5, options: .curveEaseInOut, animations:
                {
                    recognizer.view!.frame = CGRect(x: 0, y: partialY, width: self.frame.width, height: self.frame.height)
                    self.requestButton.alpha = 1
                    self.houseView.frame = CGRect(x: 16, y: 87+200, width: self.boxWidth, height: 68)
                    self.officeView.frame = CGRect(x: 16, y: 171+200, width: self.boxWidth, height: 68)
                    self.carView.frame = CGRect(x: 16, y: 255+200, width: self.boxWidth, height: 68)
                    self.gardenView.frame = CGRect(x: 16, y: 339+200, width: self.boxWidth, height: 68)
            }, completion: { (true) in

            })
        }

Solution

  • I solved the issue by adding a boolean to check whether or not the view is currently expanded and then depending on true or false I load the corresponding animations. I know this is not the best way of doing things as you are able to return the animation to its original place without having to write so much code. Regardless, it works, so I'll roll with it.

    By the way, I am very much open to anyone who can help refine this code, it definitely is in need of some TLC.

    Here is my entire method that handles my panGestureRecogniser

    // MARK: - Pan gesture
    
    @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 .began:
            print("Began")
    
            if isExpanded
            {
                animator = UIViewPropertyAnimator(duration: 0.18, curve: .easeInOut, animations: {
                    self.requestButton.alpha = 1
                    self.houseView.alpha = 0
                    self.officeView.alpha = 0
                    self.gardenView.alpha = 0
                    self.carView.alpha = 0
                    self.houseView.frame = CGRect(x: 16, y: 87+200, width: self.boxWidth, height: 68)
                    self.officeView.frame = CGRect(x: 16, y: 171+200, width: self.boxWidth, height: 68)
                    self.carView.frame = CGRect(x: 16, y: 255+200, width: self.boxWidth, height: 68)
                    self.gardenView.frame = CGRect(x: 16, y: 339+200, width: self.boxWidth, height: 68)
                })
                self.animator?.isReversed = true
            }
            else
            {
                animator = UIViewPropertyAnimator(duration: 0.18, curve: .easeInOut, animations: {
                    self.requestButton.alpha = 0
                    self.houseView.alpha = 1
                    self.officeView.alpha = 1
                    self.gardenView.alpha = 1
                    self.carView.alpha = 1
                    self.houseView.frame = CGRect(x: 16, y: 87, width: self.boxWidth, height: 68)
                    self.officeView.frame = CGRect(x: 16, y: 171, width: self.boxWidth, height: 68)
                    self.carView.frame = CGRect(x: 16, y: 255, width: self.boxWidth, height: 68)
                    self.gardenView.frame = CGRect(x: 16, y: 339, width: self.boxWidth, height: 68)
                })
                self.animator?.isReversed = false
            }
    
            animator?.startAnimation()
            animator?.pauseAnimation()
    
        case .changed:
    
            // Property animator
            let offset: CGFloat = window!.frame.height-194
            let fraction: CGFloat = (offset-currentY)/4
            let percentage = fraction/100
            print(percentage)
            animator?.fractionComplete = CGFloat(percentage)
    
            // 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
            {
                self.animator?.stopAnimation(true)
                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)
                        self.requestButton.alpha = 0
                        self.houseView.alpha = 1
                        self.officeView.alpha = 1
                        self.gardenView.alpha = 1
                        self.carView.alpha = 1
                        self.houseView.frame = CGRect(x: 16, y: 87, width: self.boxWidth, height: 68)
                        self.officeView.frame = CGRect(x: 16, y: 171, width: self.boxWidth, height: 68)
                        self.carView.frame = CGRect(x: 16, y: 255, width: self.boxWidth, height: 68)
                        self.gardenView.frame = CGRect(x: 16, y: 339, width: self.boxWidth, height: 68)
                }, completion: { (true) in
                    self.isExpanded = true
                })
            }
    
            // Snap back to partial (y) (original position)
            if currentY > partialY || currentY < partialY && currentY > halfWayPoint
            {
                self.animator?.stopAnimation(true)
                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)
                        self.requestButton.alpha = 1
                        self.houseView.alpha = 0
                        self.officeView.alpha = 0
                        self.gardenView.alpha = 0
                        self.carView.alpha = 0
                        self.houseView.frame = CGRect(x: 16, y: 87+200, width: self.boxWidth, height: 68)
                        self.officeView.frame = CGRect(x: 16, y: 171+200, width: self.boxWidth, height: 68)
                        self.carView.frame = CGRect(x: 16, y: 255+200, width: self.boxWidth, height: 68)
                        self.gardenView.frame = CGRect(x: 16, y: 339+200, width: self.boxWidth, height: 68)
                }, completion: {(true) in
                    self.isExpanded = false
                })
            }
    
        case .cancelled:
            print("Cancelled")
    
        case .failed:
            print("Failed")
    
        default:
            print("Default")
        }
    }