How can I implement “drag right to dismiss” a View Controller that's in a navigation stack?

后端 未结 6 1847
忘了有多久
忘了有多久 2020-12-07 18:24

By default, if you drag right from the left edge of the screen, it will drag away the ViewController and take it off the stack.

I want to extend this functionality t

相关标签:
6条回答
  • 2020-12-07 18:30

    Swipe Right to dismiss the View Controller

    Swift 5 Version - (Also removed the gesture recognition when swiping from right - to - left)

    Important - In ‘Attributes inspector’ of VC2, set the ‘Presentation’ value from ‘Full Screen’ to ‘Over Full Screen’. This will allow VC1 to be visible during dismissing VC2 via gesture — without it, there will be black screen behind VC2 instead of VC1.

    class ViewController: UIGestureRecognizerDelegate, UINavigationControllerDelegate {
    
        var initialTouchPoint: CGPoint = CGPoint(x: 0, y: 0)
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            navigationController?.interactivePopGestureRecognizer?.delegate = self
            let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePanGesture(_:)))
            view.addGestureRecognizer(panGesture)
        }
    
        @objc func handlePanGesture(_ sender: UIPanGestureRecognizer) {
            let touchPoint = sender.location(in: self.view?.window)
            let percent = max(sender.translation(in: view).x, 0) / view.frame.width
            let velocity = sender.velocity(in: view).x
    
            if sender.state == UIGestureRecognizer.State.began {
                initialTouchPoint = touchPoint
            } else if sender.state == UIGestureRecognizer.State.changed {
                if touchPoint.x - initialTouchPoint.x > 0 {
                    self.view.frame = CGRect(x: touchPoint.x - initialTouchPoint.x, y: 0, width: self.view.frame.size.width, height: self.view.frame.size.height)
                }
            } else if sender.state == UIGestureRecognizer.State.ended || sender.state == UIGestureRecognizer.State.cancelled {
    
                if percent > 0.5 || velocity > 1000 {
                    navigationController?.popViewController(animated: true)
                } else {
                    UIView.animate(withDuration: 0.3, animations: {
                        self.view.frame = CGRect(x: 0, y: 0, width: self.view.frame.size.width, height: self.view.frame.size.height)
                    })
                }
            }
        }
    }
    

    Hope this helps. Feel free to suggest changes if necessary.

    0 讨论(0)
  • 2020-12-07 18:37

    I think this is easier than the suggested solution and also works for all viewControllers inside that navigation and also for nested scrollviews.

    https://stackoverflow.com/a/58779146/8517882

    Just install the pod and then use EZNavigationController instead of UINavigationController to have this behavior on all view controllers inside that navigation controller.

    0 讨论(0)
  • 2020-12-07 18:46

    Made a demo project in Github
    https://github.com/rishi420/SwipeRightToPopController

    I've used UIViewControllerAnimatedTransitioning protocol

    From the doc:

    // This is used for percent driven interactive transitions, as well as for container controllers ...

    Added a UIPanGestureRecognizer to the controller's view. This is the action of the gesture:

    func handlePanGesture(panGesture: UIPanGestureRecognizer) {
    
        let percent = max(panGesture.translationInView(view).x, 0) / view.frame.width
    
        switch panGesture.state {
    
        case .Began:
            navigationController?.delegate = self
            navigationController?.popViewControllerAnimated(true)
    
        case .Changed:
            percentDrivenInteractiveTransition.updateInteractiveTransition(percent)
    
        case .Ended:
            let velocity = panGesture.velocityInView(view).x
    
            // Continue if drag more than 50% of screen width or velocity is higher than 1000
            if percent > 0.5 || velocity > 1000 {
                percentDrivenInteractiveTransition.finishInteractiveTransition()
            } else {
                percentDrivenInteractiveTransition.cancelInteractiveTransition()
            }
    
        case .Cancelled, .Failed:
            percentDrivenInteractiveTransition.cancelInteractiveTransition()
    
        default:
            break
        }
    }
    

    Steps:

    1. Calculate the percentage of drag on the view
    2. .Begin: Specify which segue to perform and assign UINavigationController delegate. delegate will be needed for InteractiveTransitioning
    3. .Changed: UpdateInteractiveTransition with percentage
    4. .Ended: Continue remaining transitioning if drag 50% or more or higher velocity else cancel
    5. .Cancelled, .Failed: cancel transitioning


    References:

    1. UIPercentDrivenInteractiveTransition
    2. https://github.com/visnup/swipe-left
    3. https://github.com/robertmryan/ScreenEdgeGestureNavigationController
    4. https://github.com/groomsy/custom-navigation-animation-transition-demo
    0 讨论(0)
  • 2020-12-07 18:48

    Swift 4 version of the accepted answer by @Warif Akhand Rishi

    Even though this answer does work there are 2 quirks that I found out about it.

    1. if you swipe left it also dismisses just as if you were swiping right.
    2. it's also very delicate because if even a slight swipe is directed in either direction it will dismiss the vc.

    Other then that it definitely works and you can swipe either right or left to dismiss.

    class ViewController: UIGestureRecognizerDelegate, UINavigationControllerDelegate {
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            navigationController?.interactivePopGestureRecognizer?.delegate = self
            let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePanGesture(_:)))
            view.addGestureRecognizer(panGesture)
        }
    
        @objc func handlePanGesture(_ gesture: UIPanGestureRecognizer){
    
            let interactiveTransition = UIPercentDrivenInteractiveTransition()
    
            let percent = max(gesture.translation(in: view).x, 0) / view.frame.width
    
            switch gesture.state {
    
            case .began:
                navigationController?.delegate = self
    
                // *** use this if the vc is PUSHED on the stack **
                navigationController?.popViewController(animated: true)
    
                // *** use this if the vc is PRESENTED **
                //navigationController?.dismiss(animated: true, completion: nil)
    
            case .changed:
                interactiveTransition.update(percent)
    
            case .ended:
                let velocity = gesture.velocity(in: view).x
    
                // Continue if drag more than 50% of screen width or velocity is higher than 1000
                if percent > 0.5 || velocity > 1000 {
                    interactiveTransition.finish()
                } else {
                    interactiveTransition.cancel()
                }
    
            case .cancelled, .failed:
                interactiveTransition.cancel()
    
            default:break
            }
        }
    }
    
    0 讨论(0)
  • 2020-12-07 18:49

    Create a pan gesture recogniser and move the interactive pop gesture recogniser's targets across.

    Add your recogniser to the pushed view controller's viewDidLoad and voila!

    let popGestureRecognizer = self.navigationController!.interactivePopGestureRecognizer!
    if let targets = popGestureRecognizer.value(forKey: "targets") as? NSMutableArray {
      let gestureRecognizer = UIPanGestureRecognizer()
      gestureRecognizer.setValue(targets, forKey: "targets")
      self.view.addGestureRecognizer(gestureRecognizer)
    }
    
    0 讨论(0)
  • 2020-12-07 18:54

    You need to investigate the interactivePopGestureRecognizer property of your UINavigationController.

    Here is a similar question with example code to hook this up.

    UINavigationController interactivePopGestureRecognizer working abnormal in iOS7

    0 讨论(0)
提交回复
热议问题