Moving view controller based on pan gesture in scrollview

前端 未结 1 1239
后悔当初
后悔当初 2021-01-24 18:30

Right now I have a scrollView that takes up the entire view controller. The code below is able to move the scrollView around but I want to move the whole view controller around.

相关标签:
1条回答
  • 2021-01-24 19:00

    I infer from your other question that you want to a gesture to dismiss this view controller. Rather than manipulating the view yourself in the gesture, I'd suggest you use custom transition with a UIPercentDrivenInteractiveTransition interaction controller, and have the gesture just manipulate the interaction controller. This achieves the same UX, but in a manner consistent with Apple's custom transitions paradigm.

    The interesting question here is how do you want to delineate between the custom dismiss transition gesture and the scroll view gesture. What you want is some gesture that is constrained in some fashion. There are tons of options here:

    • If the scroll view is left-right only, have a custom pan gesture subclass that fails if you use it horizontally;

    • If the scroll view is up-down, too, then have a top "screen edge gesture recognizer" or add some visual element that is a "grab bar" to which you tie a pan gesture

    But however you design this gesture to work, have the scroll view's gestures require that your own gesture fails before they trigger.

    For example, if you wanted a screen edge gesture recognizer, that would look like:

    class SecondViewController: UIViewController, UIViewControllerTransitioningDelegate {
    
        @IBOutlet weak var scrollView: UIScrollView!
    
        var interactionController: UIPercentDrivenInteractiveTransition?
    
        required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
    
            modalPresentationStyle = .Custom
            transitioningDelegate = self
        }
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            // ...
    
            let edge = UIScreenEdgePanGestureRecognizer(target: self, action: "handleScreenEdgeGesture:")
            edge.edges = UIRectEdge.Top
            view.addGestureRecognizer(edge)
            for gesture in scrollView.gestureRecognizers! {
                gesture.requireGestureRecognizerToFail(edge)
            }
        }
    
        // because we're using top edge gesture, hide status bar
    
        override func prefersStatusBarHidden() -> Bool {
            return true
        }
    
        func handleScreenEdgeGesture(gesture: UIScreenEdgePanGestureRecognizer) {
            switch gesture.state {
            case .Began:
                interactionController = UIPercentDrivenInteractiveTransition()
                dismissViewControllerAnimated(true, completion: nil)
            case .Changed:
                let percent = gesture.translationInView(gesture.view).y / gesture.view!.frame.size.height
                interactionController?.updateInteractiveTransition(percent)
            case .Cancelled:
                fallthrough
            case .Ended:
                if gesture.velocityInView(gesture.view).y < 0 || gesture.state == .Cancelled || (gesture.velocityInView(gesture.view).y == 0 && gesture.translationInView(gesture.view).y < view.frame.size.height / 2.0) {
                    interactionController?.cancelInteractiveTransition()
                } else {
                    interactionController?.finishInteractiveTransition()
                }
                interactionController = nil
            default: ()
            }
        }
    
        @IBAction func didTapDismissButton(sender: UIButton) {
            dismissViewControllerAnimated(true, completion: nil)
        }
    
        // MARK: UIViewControllerTransitioningDelegate
    
        func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
            return DismissAnimation()
        }
    
        func interactionControllerForDismissal(animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
            return interactionController
        }
    
    }
    
    class DismissAnimation: NSObject, UIViewControllerAnimatedTransitioning {
    
        func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval {
            return 0.25
        }
    
        func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
            let from = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)!
            let container = transitionContext.containerView()!
    
            let height = container.bounds.size.height
    
            UIView.animateWithDuration(transitionDuration(transitionContext), animations:
                {
                    from.view.transform = CGAffineTransformMakeTranslation(0, height)
                }, completion: { finished in
                    transitionContext.completeTransition(!transitionContext.transitionWasCancelled())
                }
            )
        }
    
    }
    

    Personally, I find the notion of having top and bottom screen edge gestures to be a bad UX, so I'd personally change this modal presentation to slide in from the right, and then swiping from left edge to the right feels logical, and doesn't interfere with the built in top pull down (for iOS notifications). Or if the scroll view only scrolls horizontally, then you can just have your own vertical pan gesture that fails if it's not a vertical pan.

    Or, if the scroll view only scrolls left and right, you can add your own pan gesture that is only recognized when you pull down by (a) using UIGestureRecognizerDelegate to recognize downward pans only; and (b) again setting the scroll view gestures to only recognize gestures if our pull-down gesture fails:

    override func viewDidLoad() {
        super.viewDidLoad()
    
        // ...
    
        let pan = UIPanGestureRecognizer(target: self, action: "handlePan:")
        pan.delegate = self
        view.addGestureRecognizer(pan)
    
        for gesture in scrollView.gestureRecognizers! {
            gesture.requireGestureRecognizerToFail(pan)
        }
    }
    
    func gestureRecognizerShouldBegin(gestureRecognizer: UIGestureRecognizer) -> Bool {
        if let gesture = gestureRecognizer as? UIPanGestureRecognizer {
            let translation = gesture.translationInView(gesture.view)
            let angle = atan2(translation.x, translation.y)
            return abs(angle) < CGFloat(M_PI_4 / 2.0)
        }
        return true
    }
    
    func handlePan(gesture: UIPanGestureRecognizer) {
        // the same as the `handleScreenEdgeGesture` above
    }
    

    Like I said, tons of options here. But you haven't shared enough of your design for us to advise you further on that.

    But the above illustrates the basic idea, that you shouldn't be moving the view around yourself, but rather use custom transition with your own animators and your own interactive controller.

    For more information, see WWDC 2013 Custom Transitions Using View Controllers (and also WWDC 2014 A Look Inside Presentation Controllers, if you want a little more information on the evolution of custom transitions).

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