滚动视图中基于平移手势的移动视图控制器
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)。
【讨论】:
以上是关于滚动视图中基于平移手势的移动视图控制器的主要内容,如果未能解决你的问题,请参考以下文章