在iOS中使用手势导航UIViewController

我有几个视图控制器embeddedUINavigationController(一些模式,一些推),并通过使用滑动手势浏览他们,

// Gesture recognizers UISwipeGestureRecognizer *downGesture = [[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(dismissButton)]; downGesture.direction = UISwipeGestureRecognizerDirectionDown; [downGesture setCancelsTouchesInView:NO]; [self.view addGestureRecognizer:downGesture]; 

这工作正常,但是我希望用户能够物理拖动模态表示的视图控制器,例如,向下和closures屏幕,而不仅仅是一个轻弹和一个animation做剩下的事情,或拖动到屏幕上,并捕捉到而不是点击后退button。

我已经尝试过在视图上使用平移手势来实现这一点,但是当然前面的视图控制器在它后面是不可见的,它是需要的。 这个效果如何正确实现? 视图控制器遏制? 如果是这样的话,将几个视图控制器放到堆栈上时会如何工作? 我在谈论的导航types的一个例子可以在LetterPress应用程序中find。

谢谢。

是的,自定义容器视图是要走的路(iOS 5及更高版本)。 您基本上编写自己的自定义容器,使用内置的childViewControllers属性来跟踪所有的子视图控制器。 你可能想要你自己的属性,比如说currentChildIndex ,来跟踪你当前使用哪个子控制器:

 @property (nonatomic) NSInteger currentChildIndex; 

您的父控制器应该可能有一些推和pop的非刷卡相关的导航方法,如:

 - (void)pushChildViewController:(UIViewController *)newChildController { // remove any other children that we've popped off, but are still lingering about for (NSInteger index = [self.childViewControllers count] - 1; index > self.currentChildIndex; index--) { UIViewController *childController = self.childViewControllers[index]; [childController willMoveToParentViewController:nil]; [childController.view removeFromSuperview]; [childController removeFromParentViewController]; } // get reference to the current child controller UIViewController *currentChildController = self.childViewControllers[self.currentChildIndex]; // set new child to be off to the right CGRect frame = self.containerView.bounds; frame.origin.x += frame.size.width; newChildController.view.frame = frame; // add the new child [self addChildViewController:newChildController]; [self.containerView addSubview:newChildController.view]; [newChildController didMoveToParentViewController:self]; [UIView animateWithDuration:0.5 animations:^{ CGRect frame = self.containerView.bounds; newChildController.view.frame = frame; frame.origin.x -= frame.size.width; currentChildController.view.frame = frame; }]; self.currentChildIndex++; } - (void)popChildViewController { if (self.currentChildIndex == 0) return; UIViewController *currentChildController = self.childViewControllers[self.currentChildIndex]; self.currentChildIndex--; UIViewController *previousChildController = self.childViewControllers[self.currentChildIndex]; CGRect onScreenFrame = self.containerView.bounds; CGRect offToTheRightFrame = self.containerView.bounds; offToTheRightFrame.origin.x += offToTheRightFrame.size.width; [UIView animateWithDuration:0.5 animations:^{ currentChildController.view.frame = offToTheRightFrame; previousChildController.view.frame = onScreenFrame; }]; } 

就个人而言,我有一个为这两种方法定义的协议,并确保我的父控制器configuration为符合该协议:

 @protocol ParentControllerDelegate <NSObject> - (void)pushChildViewController:(UIViewController *)newChildController; - (void)popChildViewController; @end @interface ParentViewController : UIViewController <ParentControllerDelegate> ... @end 

那么,当一个孩子想要推新孩子的时候,可以这样做:

 ChildViewController *controller = ... // instantiate and configure your next controller however you want to do that id<ParentControllerDelegate> parent = (id)self.parentViewController; NSAssert([parent conformsToProtocol:@protocol(ParentControllerDelegate)], @"Parent must conform to ParentControllerDelegate"); [parent pushChildViewController:controller]; 

当一个孩子想要自己离开,它可以这样做:

 id<ParentControllerDelegate> parent = (id)self.parentViewController; NSAssert([parent conformsToProtocol:@protocol(ParentControllerDelegate)], @"Parent must conform to ParentControllerDelegate"); [parent popChildViewController]; 

然后,父视图控制器设置了一个平移手势,以处理从一个孩子到另一个孩子的用户平移:

 - (void)handlePan:(UIPanGestureRecognizer *)gesture { static UIView *currentView; static UIView *previousView; static UIView *nextView; if (gesture.state == UIGestureRecognizerStateBegan) { // identify previous view (if any) if (self.currentChildIndex > 0) { UIViewController *previous = self.childViewControllers[self.currentChildIndex - 1]; previousView = previous.view; } else { previousView = nil; } // identify next view (if any) if (self.currentChildIndex < ([self.childViewControllers count] - 1)) { UIViewController *next = self.childViewControllers[self.currentChildIndex + 1]; nextView = next.view; } else { nextView = nil; } // identify current view UIViewController *current = self.childViewControllers[self.currentChildIndex]; currentView = current.view; } // if we're in the middle of a pan, let's adjust the center of the views accordingly CGPoint translation = [gesture translationInView:gesture.view.superview]; previousView.transform = CGAffineTransformMakeTranslation(translation.x, 0.0); currentView.transform = CGAffineTransformMakeTranslation(translation.x, 0.0); nextView.transform = CGAffineTransformMakeTranslation(translation.x, 0.0); // if we're all done, let's animate the completion (or if we didn't move far enough, // the reversal) of the pan gesture if (gesture.state == UIGestureRecognizerStateEnded || gesture.state == UIGestureRecognizerStateCancelled || gesture.state == UIGestureRecognizerStateFailed) { CGPoint center = currentView.center; CGPoint currentCenter = CGPointMake(center.x + translation.x, center.y); CGPoint offRight = CGPointMake(center.x + currentView.frame.size.width, center.y); CGPoint offLeft = CGPointMake(center.x - currentView.frame.size.width, center.y); CGPoint velocity = [gesture velocityInView:gesture.view.superview]; if ((translation.x + velocity.x * 0.5) < (-self.containerView.frame.size.width / 2.0) && nextView) { // if we finished pan to left, reset transforms previousView.transform = CGAffineTransformIdentity; currentView.transform = CGAffineTransformIdentity; nextView.transform = CGAffineTransformIdentity; // set the starting point of the animation to pick up from where // we had previously transformed the views CGPoint nextCenter = CGPointMake(nextView.center.x + translation.x, nextView.center.y); currentView.center = currentCenter; nextView.center = nextCenter; // and animate the moving of the views to their final resting points, // adjusting the currentChildIndex appropriately [UIView animateWithDuration:0.25 delay:0.0 options:UIViewAnimationOptionCurveEaseOut animations:^{ currentView.center = offLeft; nextView.center = center; self.currentChildIndex++; } completion:NULL]; } else if ((translation.x + velocity.x * 0.5) > (self.containerView.frame.size.width / 2.0) && previousView) { // if we finished pan to right, reset transforms previousView.transform = CGAffineTransformIdentity; currentView.transform = CGAffineTransformIdentity; nextView.transform = CGAffineTransformIdentity; // set the starting point of the animation to pick up from where // we had previously transformed the views CGPoint previousCenter = CGPointMake(previousView.center.x + translation.x, previousView.center.y); currentView.center = currentCenter; previousView.center = previousCenter; // and animate the moving of the views to their final resting points, // adjusting the currentChildIndex appropriately [UIView animateWithDuration:0.25 delay:0.0 options:UIViewAnimationOptionCurveEaseOut animations:^{ currentView.center = offRight; previousView.center = center; self.currentChildIndex--; } completion:NULL]; } else { [UIView animateWithDuration:0.25 delay:0.0 options:UIViewAnimationOptionCurveEaseInOut animations:^{ previousView.transform = CGAffineTransformIdentity; currentView.transform = CGAffineTransformIdentity; nextView.transform = CGAffineTransformIdentity; } completion:NULL]; } } } 

看起来你正在上下摇摄,而不是上面我使用的左右摇摄,但是希望你能得到基本的想法。


顺便说一下,在iOS 6中,您所询问的用户界面(使用手势的视图之间的滑动)可能可以使用内置的容器控制器UIPageViewController更高效地完成。 只需使用UIPageViewControllerTransitionStyleScroll的过渡样式和UIPageViewControllerTransitionStyleScroll的导航方向UIPageViewControllerNavigationOrientationHorizontal 。 不幸的是,iOS 5只允许页面curl转换,而苹果公司只是在iOS 6中引入了你想要的滚动转换,但是如果这就是你所需要的, UIPageViewController比我上面提到的更有效率地完成工作不必做任何自定义的容器调用,不需要写手势识别器等等)。

例如,您可以将“页面视图控制器”拖到故事板上,创build一个UIPageViewController子类,然后在viewDidLoad ,您需要configuration第一个页面:

 UIViewController *firstPage = [self.storyboard instantiateViewControllerWithIdentifier:@"1"]; // use whatever storyboard id your left page uses self.viewControllerStack = [NSMutableArray arrayWithObject:firstPage]; [self setViewControllers:@[firstPage] direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:NULL]; self.dataSource = self; 

那么你需要定义下面的UIPageViewControllerDataSource方法:

 - (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController { if ([viewController isKindOfClass:[LeftViewController class]]) return [self.storyboard instantiateViewControllerWithIdentifier:@"2"]; return nil; } - (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController { if ([viewController isKindOfClass:[RightViewController class]]) return [self.storyboard instantiateViewControllerWithIdentifier:@"1"]; return nil; } 

你的实现将有所不同(至less是不同的类名称和不同的故事板标识符;我也让页面视图控制器实例化下一页的控制器,当用户请求它,因为我没有保留任何强烈的引用,当我完成转换到另一个页面时,可能会被释放…你也可以在启动时实例化,然后after例程beforeafter显然不会实例化,而是在数组中查找它们),但希望你明白了。

但关键的问题是,我没有任何手势代码,没有自定义容器视图控制器代码等。更简单。