如何在视图之间实现滑动/滑动animation?

我有一些我想在iOS程序中滑动的视图。 现在,我正在使用模式风格在交叉融合animation之间滑动。 不过,我想要有一个像你在主屏幕上看到的滑动/滑动animation等等。 我不知道如何编写这样的过渡,animation风格不是可用的模式转换风格。 任何人都可以给我一个例子的代码? 它不需要是一个模式模型或任何东西,我只是发现最简单的。

自iOS 7以来,如果要为两个视图控制器之间的转换设置animation效果,则可以使用自定义转换,如WWDC 2013video使用视图控制器的video自定义转换中所述 。 例如,要自定义新的视图控制器的演示文稿,您应该:

  1. 目标视图控制器将为演示animation指定self.modalPresentationStyletransitioningDelegate

     - (instancetype)initWithCoder:(NSCoder *)coder { self = [super initWithCoder:coder]; if (self) { self.modalPresentationStyle = UIModalPresentationCustom; self.transitioningDelegate = self; } return self; } 
  2. 这个委托(在这个例子中,视图控制器本身)将符合UIViewControllerTransitioningDelegate并实现:

     - (id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source { return [[PresentAnimator alloc] init]; } // in iOS 8 and later, you'd also specify a presentation controller - (UIPresentationController *)presentationControllerForPresentedViewController:(UIViewController *)presented presentingViewController:(UIViewController *)presenting sourceViewController:(UIViewController *)source { return [[PresentationController alloc] initWithPresentedViewController:presented presentingViewController:presenting]; } 
  3. 你会实现一个animation师,将执行所需的animation:

     @interface PresentAnimator : NSObject <UIViewControllerAnimatedTransitioning> @end @implementation PresentAnimator - (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext { return 0.5; } // do whatever animation you want below - (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext { UIViewController* toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey]; UIViewController* fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey]; [[transitionContext containerView] addSubview:toViewController.view]; CGFloat width = fromViewController.view.frame.size.width; CGRect originalFrame = fromViewController.view.frame; CGRect rightFrame = originalFrame; rightFrame.origin.x += width; CGRect leftFrame = originalFrame; leftFrame.origin.x -= width / 2.0; toViewController.view.frame = rightFrame; toViewController.view.layer.shadowColor = [[UIColor blackColor] CGColor]; toViewController.view.layer.shadowRadius = 10.0; toViewController.view.layer.shadowOpacity = 0.5; [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{ fromViewController.view.frame = leftFrame; toViewController.view.frame = originalFrame; toViewController.view.layer.shadowOpacity = 0.5; } completion:^(BOOL finished) { [transitionContext completeTransition:![transitionContext transitionWasCancelled]]; }]; } @end 
  4. 您还将实现一个演示控制器,负责为您清理视图层次结构。 在这种情况下,因为我们完全覆盖了呈现视图,所以当转换完成时,我们可以从层次结构中删除它:

     @interface PresentationController: UIPresentationController @end @implementation PresentationController - (BOOL)shouldRemovePresentersView { return true; } @end 
  5. 或者,如果您希望这种手势是互动的,您还可以:

    • 创build一个交互控制器(通常是一个UIPercentDrivenInteractiveTransition );

    • 让你的UIViewControllerAnimatedTransitioning也实现interactionControllerForPresentation ,这显然会返回前面提到的交互控制器;

    • 有一个手势(或你有什么),更新interactionController

这在前面提到的使用视图控制器的自定义转换中都有描述。

例如,自定义导航控制器推/拉,请参阅导航控制器自定义转换animation


在下面,请find我的原始答案的副本,这个副本早于自定义过渡。


@ sooper的答案是正确的,CATransition可以产生你正在寻找的效果。

但顺便说一下,如果你的背景不是白色的, kCATransitionPushCATransition在转换结束时有一个奇怪的淡入和淡出,这可能会分散注意力(当在图像之间导航时,特别是它使它略微闪烁影响)。 如果你受到这个困扰,我发现这个简单的过渡是非常优美的:你可以准备好你的“下一个视图”在屏幕右侧,然后在屏幕左侧移动当前视图animation下一个视图移动到当前视图所在的位置。 请注意,在我的示例中,我在单个视图控制器中为主视图中的子视图制作animation,但您可能会有这样的想法:

 float width = self.view.frame.size.width; float height = self.view.frame.size.height; // my nextView hasn't been added to the main view yet, so set the frame to be off-screen [nextView setFrame:CGRectMake(width, 0.0, width, height)]; // then add it to the main view [self.view addSubview:nextView]; // now animate moving the current view off to the left while the next view is moved into place [UIView animateWithDuration:0.33f delay:0.0f options:UIViewAnimationOptionCurveEaseInOut | UIViewAnimationOptionAllowUserInteraction animations:^{ [nextView setFrame:currView.frame]; [currView setFrame:CGRectMake(-width, 0.0, width, height)]; } completion:^(BOOL finished){ // do whatever post processing you want (such as resetting what is "current" and what is "next") }]; 

很明显,你必须调整这个设置,所有的控制都已经设置完成了,但是这个过程非常简单,没有衰减或者其他任何事情,只是一个很好的平滑过渡。

一个告诫:首先,这个例子和CATransition例子都不像SpringBoard的主屏幕animation那样(这是你所说的),它是连续的(例如,如果你通过一个滑动的一半,你可以停下来,然后回去pipe他呢)。 这些转变是一旦启动他们,他们就发生了。 如果您需要实时交互,也可以这样做,但这是不同的。

更新:

如果你想使用连续的手势跟踪用户的手指,你可以使用UIPanGestureRecognizer而不是UISwipeGestureRecognizer ,并且我认为在这种情况下, animateWithDurationCATransition更好。 我修改了我的handlePanGesture来改变frame坐标来与用户的手势协调,然后我修改了上面的代码,刚刚完成animation,当用户放开。 工作得很好。 我不认为你可以用CATransition很容易地做到这CATransition

例如,您可以在控制器的主视图上创build一个手势处理程序:

 [self.view addGestureRecognizer:[[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePan:)]]; 

处理程序可能如下所示:

 - (void)handlePan:(UIPanGestureRecognizer *)gesture { // transform the three views by the amount of the x translation CGPoint translate = [gesture translationInView:gesture.view]; translate.y = 0.0; // I'm just doing horizontal scrolling prevView.frame = [self frameForPreviousViewWithTranslate:translate]; currView.frame = [self frameForCurrentViewWithTranslate:translate]; nextView.frame = [self frameForNextViewWithTranslate:translate]; // if we're done with gesture, animate frames to new locations if (gesture.state == UIGestureRecognizerStateCancelled || gesture.state == UIGestureRecognizerStateEnded || gesture.state == UIGestureRecognizerStateFailed) { // figure out if we've moved (or flicked) more than 50% the way across CGPoint velocity = [gesture velocityInView:gesture.view]; if (translate.x > 0.0 && (translate.x + velocity.x * 0.25) > (gesture.view.bounds.size.width / 2.0) && prevView) { // moving right (and/or flicked right) [UIView animateWithDuration:0.25 delay:0.0 options:UIViewAnimationOptionCurveEaseOut animations:^{ prevView.frame = [self frameForCurrentViewWithTranslate:CGPointZero]; currView.frame = [self frameForNextViewWithTranslate:CGPointZero]; } completion:^(BOOL finished) { // do whatever you want upon completion to reflect that everything has slid to the right // this redefines "next" to be the old "current", // "current" to be the old "previous", and recycles // the old "next" to be the new "previous" (you'd presumably. // want to update the content for the new "previous" to reflect whatever should be there UIView *tempView = nextView; nextView = currView; currView = prevView; prevView = tempView; prevView.frame = [self frameForPreviousViewWithTranslate:CGPointZero]; }]; } else if (translate.x < 0.0 && (translate.x + velocity.x * 0.25) < -(gesture.view.frame.size.width / 2.0) && nextView) { // moving left (and/or flicked left) [UIView animateWithDuration:0.25 delay:0.0 options:UIViewAnimationOptionCurveEaseOut animations:^{ nextView.frame = [self frameForCurrentViewWithTranslate:CGPointZero]; currView.frame = [self frameForPreviousViewWithTranslate:CGPointZero]; } completion:^(BOOL finished) { // do whatever you want upon completion to reflect that everything has slid to the left // this redefines "previous" to be the old "current", // "current" to be the old "next", and recycles // the old "previous" to be the new "next". (You'd presumably. // want to update the content for the new "next" to reflect whatever should be there UIView *tempView = prevView; prevView = currView; currView = nextView; nextView = tempView; nextView.frame = [self frameForNextViewWithTranslate:CGPointZero]; }]; } else { // return to original location [UIView animateWithDuration:0.25 delay:0.0 options:UIViewAnimationOptionCurveEaseOut animations:^{ prevView.frame = [self frameForPreviousViewWithTranslate:CGPointZero]; currView.frame = [self frameForCurrentViewWithTranslate:CGPointZero]; nextView.frame = [self frameForNextViewWithTranslate:CGPointZero]; } completion:NULL]; } } } 

这使用了你可能为你想要的UX定义的这些简单的frame方法:

 - (CGRect)frameForPreviousViewWithTranslate:(CGPoint)translate { return CGRectMake(-self.view.bounds.size.width + translate.x, translate.y, self.view.bounds.size.width, self.view.bounds.size.height); } - (CGRect)frameForCurrentViewWithTranslate:(CGPoint)translate { return CGRectMake(translate.x, translate.y, self.view.bounds.size.width, self.view.bounds.size.height); } - (CGRect)frameForNextViewWithTranslate:(CGPoint)translate { return CGRectMake(self.view.bounds.size.width + translate.x, translate.y, self.view.bounds.size.width, self.view.bounds.size.height); } 

您的具体实施无疑会有所不同,但希望这可以说明这个想法。

说完所有这些(补充和澄清这个旧的答案),我应该指出,我不再使用这种技术。 现在,我通常使用UIScrollView (打开“分页”)或(在iOS 6中)使用UIPageViewController 。 这使您摆脱了编写这种手势处理程序(并享受滚动条,弹跳等额外function)的业务。 在UIScrollView实现中,我只是响应scrollViewDidScroll事件,以确保我懒惰地加载必要的子视图。

你可以创build一个CATransitionanimation。 下面是一个例子,说明如何在推送当前视图的同时将第二个视图(从左侧)滑入屏幕中:

 UIView *theParentView = [self.view superview]; CATransition *animation = [CATransition animation]; [animation setDuration:0.3]; [animation setType:kCATransitionPush]; [animation setSubtype:kCATransitionFromLeft]; [animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]]; [theParentView addSubview:yourSecondViewController.view]; [self.view removeFromSuperview]; [[theParentView layer] addAnimation:animation forKey:@"showSecondViewController"]; 

如果你想在切换页面时使用与跳板相同的页面滚动/滑动效果,那么为什么不使用UIScrollView呢?

 CGFloat width = 320; CGFloat height = 400; NSInteger pages = 3; UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0,0,width,height)]; scrollView.contentSize = CGSizeMake(width*pages, height); scrollView.pagingEnabled = YES; 

然后使用UIPageControl来获取这些点。 🙂