f.p*_*ion 4 uiviewcontroller uinavigationcontroller uigesturerecognizer ios
我有几个嵌入在UINavigationController中的视图控制器(一些模态,一些模型),并使用滑动手势导航它们:
// Gesture recognizers
UISwipeGestureRecognizer *downGesture = [[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(dismissButton)];
downGesture.direction = UISwipeGestureRecognizerDirectionDown;
[downGesture setCancelsTouchesInView:NO];
[self.view addGestureRecognizer:downGesture];
Run Code Online (Sandbox Code Playgroud)
这样可以正常工作,但是我希望用户能够物理地拖动模态显示的视图控制器,例如,向下和离开屏幕,而不仅仅是轻弹和动画完成剩下的工作,或者在屏幕上拖动并捕捉到上一个视图而不是点击后退按钮.
我已尝试在视图上使用平移手势实现此功能,但当然前面的视图控制器在它后面是不可见的,它需要它.这种效果如何正确实现?用视图控制器控制?如果是这样,当将几个视图控制器推送到堆栈时,它会如何工作?我正在谈论的导航类型的一个例子可以在LetterPress应用程序中找到.
谢谢.
是的,自定义容器视图是可行的方法(iOS 5及更高版本).您基本上编写自己的自定义容器,使用内置childViewControllers属性来跟踪所有子视图控制器.例如,您可能希望自己的属性currentChildIndex跟踪您当前使用的子控制器:
@property (nonatomic) NSInteger currentChildIndex;
Run Code Online (Sandbox Code Playgroud)
您的父控制器可能应该有一些用于非滑动相关导航的推送和弹出方法,例如:
- (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;
}];
}
Run Code Online (Sandbox Code Playgroud)
就个人而言,我有一个为这两种方法定义的协议,并确保我的父控制器配置为符合该协议:
@protocol ParentControllerDelegate <NSObject>
- (void)pushChildViewController:(UIViewController *)newChildController;
- (void)popChildViewController;
@end
@interface ParentViewController : UIViewController <ParentControllerDelegate>
...
@end
Run Code Online (Sandbox Code Playgroud)
然后,当一个孩子想要推一个新孩子时,它可以这样做:
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];
Run Code Online (Sandbox Code Playgroud)
当一个孩子想要脱身时,它可以这样做:
id<ParentControllerDelegate> parent = (id)self.parentViewController;
NSAssert([parent conformsToProtocol:@protocol(ParentControllerDelegate)], @"Parent must conform to ParentControllerDelegate");
[parent popChildViewController];
Run Code Online (Sandbox Code Playgroud)
然后父视图控制器设置了平移手势,以处理用户从一个孩子到另一个孩子的平移:
- (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];
}
}
}
Run Code Online (Sandbox Code Playgroud)
看起来你正在进行上下平移,而不是我上面使用的左右平移,但希望你得到基本的想法.
顺便说一句,在iOS 6中,您可能会询问用户界面(使用手势在视图之间滑动),使用内置容器控制器可以更有效地完成UIPageViewController.只需使用过渡方式UIPageViewControllerTransitionStyleScroll和导航方向UIPageViewControllerNavigationOrientationHorizontal.不幸的是,iOS 5只允许页面卷曲过渡,Apple只在iOS 6中引入了你想要的滚动过渡,但如果这就是你需要的,UIPageViewController那么比我上面列出的更有效地完成工作(你不要我必须做任何自定义容器调用,不写手势识别器等).
例如,您可以将"页面视图控制器"拖到故事板上,创建UIPageViewController子类然后在viewDidLoad,您需要配置第一页:
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;
Run Code Online (Sandbox Code Playgroud)
然后,您需要定义以下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;
}
Run Code Online (Sandbox Code Playgroud)
您的实现会有所不同(至少不同的类名和不同的故事板标识符;当用户要求时,我也让页面视图控制器实例化下一页的控制器,因为我没有保留对它们的任何强引用,当我完成转换到另一个页面时会释放...你可以在启动时实例化这两个,然后这些before和after例程显然不会实例化,而是在数组中查找它们,但希望你得到这个想法.
但关键问题是我没有任何手势代码,没有自定义容器视图控制器代码等.更简单.