简体   繁体   中英

Moving view controller based on pan gesture in scrollview

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. How would I do that?

override func viewDidLoad() {
        pan = UIPanGestureRecognizer(target: self, action: "handlePan:")
        self.scrollview.addGestureRecognizer(pan)
}

func handlePan(recognizer:UIPanGestureRecognizer!) {

    switch recognizer.state {
    case .Changed:
        handlePanChanged(recognizer); break
    case .Ended:
        handlePanTerminated(recognizer); break
    case .Cancelled:
        handlePanTerminated(recognizer); break
    case .Failed:
        handlePanTerminated(recognizer); break
    default: break
    }
}

func handlePanChanged(recognizer:UIPanGestureRecognizer!) {
    if let view = recognizer.view {
        var translation = recognizer.translationInView(self.view)
        println("moving")
        view.center = CGPointMake(view.center.x, view.center.y + translation.y);
        recognizer.setTranslation(CGPointZero, inView: self.view)
    }
}

I've tried different variations of "self.view.center ...." "UIApplication.sharedApplication.rootViewController.view.center.." etc.

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).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM