滚动视图中基于平移手势的移动视图控制器

Posted

技术标签:

【中文标题】滚动视图中基于平移手势的移动视图控制器【英文标题】:Moving view controller based on pan gesture in scrollview 【发布时间】:2015-12-05 09:48:35 【问题描述】:

现在我有一个滚动视图,它占据了整个视图控制器。下面的代码能够移动滚动视图,但我想移动整个视图控制器。我该怎么做?

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)
    

我尝试了“self.view.center ....”“UIApplication.sharedApplication.rootViewController.view.center..”等的不同变体。

【问题讨论】:

【参考方案1】:

我从你的other question 推断出你想要一个手势来关闭这个视图控制器。我建议您使用带有UIPercentDrivenInteractiveTransition 交互控制器的自定义转换,而不是自己在手势中操纵视图,并让手势仅操纵交互控制器。这实现了相同的用户体验,但方式与 Apple 的自定义转换范例一致。

这里有趣的问题是您希望如何在自定义关闭过渡手势和滚动视图手势之间进行描述。你想要的是某种以某种方式受到限制的姿态。这里有很多选择:

如果滚动视图仅为左右滚动,则有一个自定义平移手势子类,如果水平使用它会失败;

如果滚动视图也是上下滚动视图,则使用顶部“屏幕边缘手势识别器”或添加一些视觉元素,即“抓取栏”,您可以将平移手势绑定到该元素

但是,无论您如何设计此手势,滚动视图的手势都需要您自己的手势在触发之前失败。

例如,如果您想要一个屏幕边缘手势识别器,则如下所示:

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


就我个人而言,我发现拥有顶部和底部屏幕边缘手势的想法是一个糟糕的用户体验,因此我个人会将此模式演示更改为从右侧滑入,然后从左侧边缘滑动到右侧感觉合乎逻辑,并且不会干扰内置的顶部下拉(用于 ios 通知)。或者,如果滚动视图仅水平滚动,那么您可以只使用自己的垂直平移手势,如果它不是垂直平移,则会失败。

或者,如果滚动视图只左右滚动,您可以添加自己的平移手势,该手势仅在您下拉时被识别 (a) 使用UIGestureRecognizerDelegate 仅识别向下平移; (b) 再次将滚动视图手势设置为仅在我们的下拉手势失败时识别手势:

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

就像我说的,这里有很多选择。但是您还没有分享足够多的设计,我们无法就此提供进一步的建议。

但上面说明了基本思想,即您不应该在自己周围移动视图,而是使用您自己的动画师和您自己的交互式控制器使用自定义过渡。

有关详细信息,请参阅 WWDC 2013 Custom Transitions Using View Controllers(如果您想了解更多有关自定义过渡演变的信息,还可以参阅 WWDC 2014 A Look Inside Presentation Controllers)。

【讨论】:

以上是关于滚动视图中基于平移手势的移动视图控制器的主要内容,如果未能解决你的问题,请参考以下文章

带有平移手势冲突的滚动视图和侧边菜单

基于平移手势翻译的滚动scrollview

UICollectionView 子视图平移移动而不滚动

如何将平移手势从滚动视图转发到另一个滚动视图

是否可以禁用滑动手势并仅使平移手势在滚动视图中工作?

滚动视图平移防止捏手势?