如何改变推和弹出动画在一个基于导航的应用程序

我有一个基于导航的应用程序,我想改变推送和弹出动画的动画。我该怎么做呢?

编辑2018

这个问题有很多答案,现在已经有很长一段时间了,我重新选择了我认为最相关的答案。如果有人有不同的想法,请在评论中告诉我

227298 次浏览

我不知道有什么方法可以公开更改过渡动画。

如果“后退”按钮不是必需的,你应该使用模态视图控制器来拥有“从底部推”/“翻转”/“淡出”/(≥3.2)“页面卷曲”过渡。


私人端,方法-pushViewController:animated:调用未记录的方法-pushViewController:transition:forceImmediate:,因此,例如,如果你想要从左到右的翻转转换,你可以使用

[navCtrler pushViewController:ctrler transition:10 forceImmediate:NO];

但是,您不能以这种方式更改“pop”过渡。

使用私人通话是一个坏主意,因为苹果不再批准这样做的应用程序。 也许你可以试试这个:

//Init Animation
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration: 0.50];




[UIView setAnimationTransition:UIViewAnimationTransitionCurlUp forView:self.navigationController.view cache:YES];


//Create ViewController
MyViewController *myVC = [[MyViewController alloc] initWith...];


[self.navigationController pushViewController:myVC animated:NO];
[myVC release];


//Start Animation
[UIView commitAnimations];

我找到了一种温和的递归方法来实现这个目的。我有一个实例变量BOOL,我用它来阻止正常的弹出动画,并代替我自己的非动画弹出消息。该变量最初设置为NO。当点击后退按钮时,委托方法将其设置为YES,并向导航栏发送一个新的非动画弹出消息,从而再次调用相同的委托方法,这一次将变量设置为YES。当变量被设置为YES时,委托方法将其设置为NO并返回YES以允许发生非动画弹出。在第二个委托调用返回后,我们最终回到了第一个委托调用,在那里返回NO,阻塞了原始的动画弹出!其实没听起来那么乱。我的shouldPopItem方法是这样的:

- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item
{
if ([[navigationBar items] indexOfObject:item] == 1)
{
[expandedStack restack];
}


if (!progPop)
{
progPop = YES;
[navBar popNavigationItemAnimated:NO];
return NO;
}
else
{
progPop = NO;
return YES;
}
}

对我有用。

我最近也在尝试做类似的事情。我决定我不喜欢UINavigationController的滑动动画,但我也不想做UIView给你的动画,比如旋度之类的。我想做一个交叉淡出之间的观点,当我推或弹出。

这里的问题涉及到,视图实际上是在移除视图或在当前视图上方弹出一个视图,因此渐变不起作用。我想出的解决方案是把我的新视图作为子视图添加到UIViewController的堆栈上的当前顶视图。我加一个alpha为0,然后做横向渐隐。当动画序列结束时,我将视图推到堆栈上,而不进行动画。然后我回到旧的topView,清理我已经更改的东西。

它比这稍微复杂一点,因为你有navigationItems,你必须调整,使过渡看起来正确。此外,如果你做任何旋转,你必须调整帧的大小,因为你添加的视图作为子视图,使他们正确地显示在屏幕上。下面是我使用的一些代码。我子类化了UINavigationController并覆盖了push和pop方法。

-(void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated
{
UIViewController *currentViewController = [self.viewControllers lastObject];
//if we don't have a current controller, we just do a normal push
if(currentViewController == nil)
{
[super pushViewController:viewController animated:animated];
return;
}
//if no animation was requested, we can skip the cross fade
if(!animation)
{
[super pushViewController:viewController animated:NO];
return;
}
//start the cross fade.  This is a tricky thing.  We basically add the new view
//as a subview of the current view, and do a cross fade through alpha values.
//then we push the new view on the stack without animating it, so it seemlessly is there.
//Finally we remove the new view that was added as a subview to the current view.


viewController.view.alpha = 0.0;
//we need to hold onto this value, we'll be releasing it later
NSString *title = [currentViewController.title retain];


//add the view as a subview of the current view
[currentViewController.view addSubview:viewController.view];
[currentViewController.view bringSubviewToFront:viewController.view];
UIBarButtonItem *rButtonItem = currentViewController.navigationItem.rightBarButtonItem;
UIBarButtonItem *lButtonItem = currentViewController.navigationItem.leftBarButtonItem;


NSArray *array = nil;


//if we have a right bar button, we need to add it to the array, if not, we will crash when we try and assign it
//so leave it out of the array we are creating to pass as the context.  I always have a left bar button, so I'm not checking to see if it is nil. Its a little sloppy, but you may want to be checking for the left BarButtonItem as well.
if(rButtonItem != nil)
array = [[NSArray alloc] initWithObjects:currentViewController,viewController,title,lButtonItem,rButtonItem,nil];
else {
array = [[NSArray alloc] initWithObjects:currentViewController,viewController,title,lButtonItem,nil];
}


//remove the right bar button for our transition
[currentViewController.navigationItem setRightBarButtonItem:nil animated:YES];
//remove the left bar button and create a backbarbutton looking item
//[currentViewController.navigationItem setLeftBarButtonItem:nil animated:NO];


//set the back button
UIBarButtonItem *backButton = [[UIBarButtonItem alloc] initWithTitle:title style:kButtonStyle target:self action:@selector(goBack)];
[currentViewController.navigationItem setLeftBarButtonItem:backButton animated:YES];
[viewController.navigationItem setLeftBarButtonItem:backButton animated:NO];
[backButton release];


[currentViewController setTitle:viewController.title];


[UIView beginAnimations:@"push view" context:array];
[UIView setAnimationDidStopSelector:@selector(animationForCrossFadePushDidStop:finished:context:)];
[UIView setAnimationDelegate:self];
[UIView setAnimationDuration:0.80];
[viewController.view setAlpha: 1.0];
[UIView commitAnimations];
}


-(void)animationForCrossFadePushDidStop:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context
{


UIViewController *c = [(NSArray*)context objectAtIndex:0];
UIViewController *n = [(NSArray*)context objectAtIndex:1];
NSString *title     = [(NSArray *)context objectAtIndex:2];
UIBarButtonItem *l = [(NSArray *)context objectAtIndex:3];
UIBarButtonItem *r = nil;
//not all views have a right bar button, if we look for it and it isn't in the context,
//we'll crash out and not complete the method, but the program won't crash.
//So, we need to check if it is there and skip it if it isn't.
if([(NSArray *)context count] == 5)
r = [(NSArray *)context objectAtIndex:4];


//Take the new view away from being a subview of the current view so when we go back to it
//it won't be there anymore.
[[[c.view subviews] lastObject] removeFromSuperview];
[c setTitle:title];
[title release];
//set the search button
[c.navigationItem setLeftBarButtonItem:l animated:NO];
//set the next button
if(r != nil)
[c.navigationItem setRightBarButtonItem:r animated:NO];




[super pushViewController:n animated:NO];


}

正如我在代码中提到的,我总是有一个左栏按钮项,所以在把它放在作为动画委托上下文传递的数组中之前,我不检查它是否为nil。如果你这样做,你可能想要检查一下。

我发现的问题是,如果你崩溃在委托方法,它不会崩溃的程序。它只是停止委托完成,但你不会得到任何类型的警告 因此,由于我在那个委托例程中进行清理,它导致了一些奇怪的视觉行为,因为它没有完成清理

我创建的后退按钮调用一个“goBack”方法,该方法只调用pop例程。

-(void)goBack
{
[self popViewControllerAnimated:YES];
}

还有,这是我的流行曲目。

-(UIViewController *)popViewControllerAnimated:(BOOL)animated
{
//get the count for the number of viewControllers on the stack
int viewCount = [[self viewControllers] count];
//get the top view controller on the stack
UIViewController *topViewController = [self.viewControllers objectAtIndex:viewCount - 1];
//get the next viewController after the top one (this will be the new top one)
UIViewController *newTopViewController = [self.viewControllers objectAtIndex:viewCount - 2];


//if no animation was requested, we can skip the cross fade
if(!animated)
{
[super popViewControllerAnimated:NO];
return topViewController;
}






//start of the cross fade pop.  A bit tricky.  We need to add the new top controller
//as a subview of the curent view controler with an alpha of 0.  We then do a cross fade.
//After that we pop the view controller off the stack without animating it.
//Then the cleanup happens: if the view that was popped is not released, then we
//need to remove the subview we added and change some titles back.
newTopViewController.view.alpha = 0.0;
[topViewController.view addSubview:newTopViewController.view];
[topViewController.view bringSubviewToFront:newTopViewController.view];
NSString *title = [topViewController.title retain];
UIBarButtonItem *lButtonItem = topViewController.navigationItem.leftBarButtonItem;
UIBarButtonItem *rButtonItem = topViewController.navigationItem.rightBarButtonItem;


//set the new buttons on top of the current controller from the new top controller
if(newTopViewController.navigationItem.leftBarButtonItem != nil)
{
[topViewController.navigationItem setLeftBarButtonItem:newTopViewController.navigationItem.leftBarButtonItem animated:YES];
}
if(newTopViewController.navigationItem.rightBarButtonItem != nil)
{
[topViewController.navigationItem setRightBarButtonItem:newTopViewController.navigationItem.rightBarButtonItem animated:YES];
}


[topViewController setTitle:newTopViewController.title];
//[topViewController.navigationItem.leftBarButtonItem setTitle:newTopViewController.navigationItem.leftBarButtonItem.title];


NSArray *array = nil;
if(rButtonItem != nil)
array = [[NSArray alloc] initWithObjects:topViewController,title,lButtonItem,rButtonItem,nil];
else {
array = [[NSArray alloc] initWithObjects:topViewController,title,lButtonItem,nil];
}




[UIView beginAnimations:@"pop view" context:array];
[UIView setAnimationDidStopSelector:@selector(animationForCrossFadePopDidStop:finished:context:)];
[UIView setAnimationDelegate:self];
[UIView setAnimationDuration:0.80];
[newTopViewController.view setAlpha: 1.0];
[UIView commitAnimations];
return topViewController;


}


-(void)animationForCrossFadePopDidStop:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context
{


UIViewController *c = [(NSArray *)context objectAtIndex:0];
//UIViewController *n = [(NSArray *)context objectAtIndex:1];
NSString *title = [(NSArray *)context objectAtIndex:1];
UIBarButtonItem *l = [(NSArray *)context objectAtIndex:2];
UIBarButtonItem *r = nil;






//Not all views have a right bar button.  If we look for one that isn't there
// we'll crash out and not complete this method, but the program will continue.
//So we need to check if it is therea nd skip it if it isn't.
if([(NSArray *)context count] == 4)
r = [(NSArray *)context objectAtIndex:3];


//pop the current view from the stack without animation
[super popViewControllerAnimated:NO];


//if what was the current veiw controller is not nil, then lets correct the changes
//we made to it.
if(c != nil)
{
//remove the subview we added for the transition
[[c.view.subviews lastObject] removeFromSuperview];
//reset the title we changed
c.title = title;
[title release];
//replace the left bar button that we changed
[c.navigationItem setLeftBarButtonItem:l animated:NO];
//if we were passed a right bar button item, replace that one as well
if(r != nil)
[c.navigationItem setRightBarButtonItem:r animated:NO];
else {
[c.navigationItem setRightBarButtonItem:nil animated:NO];
}




}
}

差不多就是这样。如果想实现旋转,还需要一些额外的代码。你需要设置你添加为子视图的视图的帧大小,在你显示它们之前,否则你会遇到问题,方向是横向的,但你最后一次看到之前的视图是纵向的。然后你把它作为子视图添加,然后淡入,但它显示为竖屏,然后当我们无动画弹出时,相同的视图,但在堆栈中的那个,现在是横屏。整件事看起来有点怪异。每个人对旋转的实现都有点不同,所以我没有在这里包括我的代码。

希望它能帮助到一些人。我到处都在找这样的东西,但什么也没找到。我不认为这是完美的答案,但在这一点上,它对我来说真的很有效。

我做了以下工作,它很好..并且简单易懂。

CATransition* transition = [CATransition animation];
transition.duration = 0.5;
transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
transition.type = kCATransitionFade; //kCATransitionMoveIn; //, kCATransitionPush, kCATransitionReveal, kCATransitionFade
//transition.subtype = kCATransitionFromTop; //kCATransitionFromLeft, kCATransitionFromRight, kCATransitionFromTop, kCATransitionFromBottom
[self.navigationController.view.layer addAnimation:transition forKey:nil];
[[self navigationController] popViewControllerAnimated:NO];

push也是一样。


Swift 3.0版本:

let transition = CATransition()
transition.duration = 0.5
transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
transition.type = kCATransitionFade
self.navigationController?.view.layer.add(transition, forKey: nil)
_ = self.navigationController?.popToRootViewController(animated: false)

这就是我总是设法完成这项任务的方法。

推动:

MainView *nextView=[[MainView alloc] init];
[UIView  beginAnimations:nil context:NULL];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationDuration:0.75];
[self.navigationController pushViewController:nextView animated:NO];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:self.navigationController.view cache:NO];
[UIView commitAnimations];
[nextView release];

流行:

[UIView  beginAnimations:nil context:NULL];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationDuration:0.75];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft forView:self.navigationController.view cache:NO];
[UIView commitAnimations];


[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDelay:0.375];
[self.navigationController popViewControllerAnimated:NO];
[UIView commitAnimations];
< p >
我还是从这里得到了很多反馈所以我将继续更新它,使用动画块这是苹果推荐的制作动画的方法。

对于Push:

MainView *nextView = [[MainView alloc] init];
[UIView animateWithDuration:0.75
animations:^{
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[self.navigationController pushViewController:nextView animated:NO];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:self.navigationController.view cache:NO];
}];

对于Pop:

[UIView animateWithDuration:0.75
animations:^{
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft forView:self.navigationController.view cache:NO];
}];
[self.navigationController popViewControllerAnimated:NO];
在示例应用程序中,检查这个变化。 https://github.com/mpospese/MPFoldTransition/ < / p >
#pragma mark - UINavigationController(MPFoldTransition)


@implementation UINavigationController(MPFoldTransition)


//- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated
- (void)pushViewController:(UIViewController *)viewController foldStyle:(MPFoldStyle)style
{
[MPFoldTransition transitionFromViewController:[self visibleViewController]
toViewController:viewController
duration:[MPFoldTransition defaultDuration]
style:style
completion:^(BOOL finished) {
[self pushViewController:viewController animated:NO];
}
];
}


- (UIViewController *)popViewControllerWithFoldStyle:(MPFoldStyle)style
{
UIViewController *toController = [[self viewControllers] objectAtIndex:[[self viewControllers] count] - 2];


[MPFoldTransition transitionFromViewController:[self visibleViewController]
toViewController:toController
duration:[MPFoldTransition defaultDuration]
style:style
completion:^(BOOL finished) {
[self popViewControllerAnimated:NO];
}
];


return toController;
}

虽然这里所有的答案都很棒,而且大多数工作得很好,但有一个稍微简单一点的方法可以达到同样的效果……

推动:

  NextViewController *nextViewController = [[NextViewController alloc] init];


// Shift the view to take the status bar into account
CGRect frame = nextViewController.view.frame;
frame.origin.y -= 20;
frame.size.height += 20;
nextViewController.view.frame = frame;


[UIView transitionFromView:self.navigationController.topViewController.view toView:nextViewController.view duration:0.5 options:UIViewAnimationOptionTransitionFlipFromRight completion:^(BOOL finished) {
[self.navigationController pushViewController:nextViewController animated:NO];
}];

流行:

  int numViewControllers = self.navigationController.viewControllers.count;
UIView *nextView = [[self.navigationController.viewControllers objectAtIndex:numViewControllers - 2] view];


[UIView transitionFromView:self.navigationController.topViewController.view toView:nextView duration:0.5 options:UIViewAnimationOptionTransitionFlipFromLeft completion:^(BOOL finished) {
[self.navigationController popViewControllerAnimated:NO];
}];}

只使用:

ViewController *viewController = [[ViewController alloc] init];


UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:viewController];
navController.navigationBarHidden = YES;


[self presentViewController:navController animated:YES completion: nil];
[viewController release];
[navController release];

看到我的回答对于这个问题的方法来做它在更少的代码行。这个方法允许你以任何你喜欢的方式动画一个新视图控制器的伪“Push”,当动画完成时,它就像你使用标准的Push方法一样设置导航控制器。我的示例允许您从左边或从右边对滑入进行动画操作。 为方便起见,这里重复代码:

-(void) showVC:(UIViewController *) nextVC rightToLeft:(BOOL) rightToLeft {
[self addChildViewController:neighbor];
CGRect offscreenFrame = self.view.frame;
if(rightToLeft) {
offscreenFrame.origin.x = offscreenFrame.size.width * -1.0;
} else if(direction == MyClimbDirectionRight) {
offscreenFrame.origin.x = offscreenFrame.size.width;
}
[[neighbor view] setFrame:offscreenFrame];
[self.view addSubview:[neighbor view]];
[neighbor didMoveToParentViewController:self];
[UIView animateWithDuration:0.5 animations:^{
[[neighbor view] setFrame:self.view.frame];
} completion:^(BOOL finished){
[neighbor willMoveToParentViewController:nil];
[neighbor.view removeFromSuperview];
[neighbor removeFromParentViewController];
[[self navigationController] pushViewController:neighbor animated:NO];
NSMutableArray *newStack = [[[self navigationController] viewControllers] mutableCopy];
[newStack removeObjectAtIndex:1]; //self, just below top
[[self navigationController] setViewControllers:newStack];
}];
}

看一下ADTransitionController,一个用自定义过渡动画替换UINavigationController的drop(它的API与UINavigationController的API匹配),这是我们在Applidium创建的。

你可以为流行动作使用不同的预定义动画,比如刷卡褪色多维数据集旋转木马变焦等等。

意识到这是一个老问题。我仍然想张贴这个答案,因为我有一些问题弹出几个viewControllers与建议的答案。我的解决方案是子类UINavigationController和覆盖所有的pop和push方法。

FlippingNavigationController.h

@interface FlippingNavigationController : UINavigationController


@end

FlippingNavigationController.m:

#import "FlippingNavigationController.h"


#define FLIP_DURATION 0.5


@implementation FlippingNavigationController


- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated
{
[UIView transitionWithView:self.view
duration:animated?FLIP_DURATION:0
options:UIViewAnimationOptionCurveEaseInOut | UIViewAnimationOptionTransitionFlipFromRight
animations:^{ [super pushViewController:viewController
animated:NO]; }
completion:nil];
}


- (UIViewController *)popViewControllerAnimated:(BOOL)animated
{
return [[self popToViewController:[self.viewControllers[self.viewControllers.count - 2]]
animated:animated] lastObject];
}


- (NSArray *)popToRootViewControllerAnimated:(BOOL)animated
{
return [self popToViewController:[self.viewControllers firstObject]
animated:animated];
}


- (NSArray *)popToViewController:(UIViewController *)viewController animated:(BOOL)animated
{
__block NSArray* viewControllers = nil;


[UIView transitionWithView:self.view
duration:animated?FLIP_DURATION:0
options:UIViewAnimationOptionCurveEaseInOut | UIViewAnimationOptionTransitionFlipFromLeft
animations:^{ viewControllers = [super popToViewController:viewController animated:NO]; }
completion:nil];


return viewControllers;
}


@end

使用iJordan的答案作为灵感,为什么不简单地在UINavigationController上创建一个Category来在整个应用程序中使用,而不是复制/粘贴这个动画代码的所有地方?

UINavigationController + Animation.h

@interface UINavigationController (Animation)


- (void) pushViewControllerWithFlip:(UIViewController*) controller;


- (void) popViewControllerWithFlip;


@end

UINavigationController + Animation.m

@implementation UINavigationController (Animation)


- (void) pushViewControllerWithFlip:(UIViewController *) controller
{
[UIView animateWithDuration:0.50
animations:^{
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[self pushViewController:controller animated:NO];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:self.view cache:NO];
}];
}


- (void) popViewControllerWithFlip
{
[UIView animateWithDuration:0.5
animations:^{
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:self.view cache:NO];
}];


[self popViewControllerAnimated:NO];
}


@end

然后简单地导入UINavigationController+Animation.h文件并正常调用它:

[self.navigationController pushViewControllerWithFlip:[[NewViewController alloc] init]];


[self.navigationController popViewControllerWithFlip];

为推动

CATransition *transition = [CATransition animation];
transition.duration = 0.3;
transition.type = kCATransitionFade;
//transition.subtype = kCATransitionFromTop;


[self.navigationController.view.layer addAnimation:transition forKey:kCATransition];
[self.navigationController pushViewController:ViewControllerYouWantToPush animated:NO];

为流行

CATransition *transition = [CATransition animation];
transition.duration = 0.3;
transition.type = kCATransitionFade;
//transition.subtype = kCATransitionFromTop;


[self.navigationController.view.layer addAnimation:transition forKey:kCATransition];
[self.navigationController popViewControllerAnimated:NO];

以下是我如何在Swift中做同样的事情:

推动:

    UIView.animateWithDuration(0.75, animations: { () -> Void in
UIView.setAnimationCurve(UIViewAnimationCurve.EaseInOut)
self.navigationController!.pushViewController(nextView, animated: false)
UIView.setAnimationTransition(UIViewAnimationTransition.FlipFromRight, forView: self.navigationController!.view!, cache: false)
})

流行:

实际上,我对上面的一些回答做了一些不同的处理——但由于我是Swift开发的新手,这可能是不对的。我重写了viewWillDisappear:animated:并在其中添加了弹出代码:

    UIView.animateWithDuration(0.75, animations: { () -> Void in
UIView.setAnimationCurve(UIViewAnimationCurve.EaseInOut)
UIView.setAnimationTransition(UIViewAnimationTransition.FlipFromLeft, forView: self.navigationController!.view, cache: false)
})


super.viewWillDisappear(animated)

@Magnus回答,只有Swift(2.0)才会这样

    let transition = CATransition()
transition.duration = 0.5
transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
transition.type = kCATransitionPush
transition.subtype = kCATransitionFromTop
self.navigationController!.view.layer.addAnimation(transition, forKey: nil)
let writeView : WriteViewController = self.storyboard?.instantiateViewControllerWithIdentifier("WriteView") as! WriteViewController
self.navigationController?.pushViewController(writeView, animated: false)

旁注:

你也可以用Segue做到这一点,只是在prepareForSegueshouldPerformSegueWithIdentifier中实现它。然而,这将保留默认动画。要解决这个问题,你必须转到故事板,单击Segue,取消选中“Animates”框。但这将限制你的应用在IOS 9.0及以上(至少当我在Xcode 7中这样做时)。

在segue中,最后两行应该替换为:

self.navigationController?.popViewControllerAnimated(false)

即使我设为false,它还是会忽略它。

有UINavigationControllerDelegate和uiviewcontrolleranimatedtransiating你可以改变任何你想要的动画。

例如,这是VC的垂直流行动画:

@objc class PopAnimator: NSObject, UIViewControllerAnimatedTransitioning {


func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval {
return 0.5
}


func animateTransition(transitionContext: UIViewControllerContextTransitioning) {


let fromViewController = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)!
let toViewController = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)!
let containerView = transitionContext.containerView()
let bounds = UIScreen.mainScreen().bounds
containerView!.insertSubview(toViewController.view, belowSubview: fromViewController.view)
toViewController.view.alpha = 0.5


let finalFrameForVC = fromViewController.view.frame


UIView.animateWithDuration(transitionDuration(transitionContext), animations: {
fromViewController.view.frame = CGRectOffset(finalFrameForVC, 0, bounds.height)
toViewController.view.alpha = 1.0
}, completion: {
finished in
transitionContext.completeTransition(!transitionContext.transitionWasCancelled())
})
}

然后

func navigationController(navigationController: UINavigationController, animationControllerForOperation operation: UINavigationControllerOperation, fromViewController fromVC: UIViewController, toViewController toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
if operation == .Pop {
return PopAnimator()
}
return nil;
}
< p >有用的教程 https://www.objc.io/issues/5-ios7/view-controller-transitions/ < / p >

记住,在斯威夫特中,扩展绝对是你的朋友!

public extension UINavigationController {


/**
Pop current view controller to previous view controller.


- parameter type:     transition animation type.
- parameter duration: transition animation duration.
*/
func pop(transitionType type: String = kCATransitionFade, duration: CFTimeInterval = 0.3) {
self.addTransition(transitionType: type, duration: duration)
self.popViewControllerAnimated(false)
}


/**
Push a new view controller on the view controllers's stack.


- parameter vc:       view controller to push.
- parameter type:     transition animation type.
- parameter duration: transition animation duration.
*/
func push(viewController vc: UIViewController, transitionType type: String = kCATransitionFade, duration: CFTimeInterval = 0.3) {
self.addTransition(transitionType: type, duration: duration)
self.pushViewController(vc, animated: false)
}


private func addTransition(transitionType type: String = kCATransitionFade, duration: CFTimeInterval = 0.3) {
let transition = CATransition()
transition.duration = duration
transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
transition.type = type
self.view.layer.addAnimation(transition, forKey: nil)
}


}

由于这是谷歌上的排名第一的结果,我想我要分享我认为是最理智的方式;也就是使用ios7 +过渡API。我用Swift 3在iOS 10上实现了这个功能。

如果你创建一个UINavigationController的子类并返回一个符合UIViewControllerAnimatedTransitioning协议的类的实例,那么将这与UINavigationController如何在两个视图控制器之间动画结合起来非常简单。

例如,下面是我的UINavigationController子类:

class NavigationController: UINavigationController {
init() {
super.init(nibName: nil, bundle: nil)


delegate = self
}


required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}


extension NavigationController: UINavigationControllerDelegate {


public func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationControllerOperation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return NavigationControllerAnimation(operation: operation)
}


}

你可以看到我将UINavigationControllerDelegate设置为自身,并且在我的子类的扩展中,我实现了UINavigationControllerDelegate中的方法,它允许你返回一个自定义动画控制器(即NavigationControllerAnimation)。这个自定义动画控制器将取代库存动画为您。

你可能想知道为什么我通过NavigationControllerAnimation实例的初始化式将操作传递给它。我这样做是为了在NavigationControllerAnimationUIViewControllerAnimatedTransitioning协议的实现中,我知道操作是什么(即,'push'或'pop')。这有助于知道我应该做什么样的动画。大多数情况下,您希望根据操作执行不同的动画。

其余的都是标准的。在UIViewControllerAnimatedTransitioning协议中实现两个必需的函数,并按你喜欢的方式进行动画:

class NavigationControllerAnimation: NSObject, UIViewControllerAnimatedTransitioning {


let operation: UINavigationControllerOperation


init(operation: UINavigationControllerOperation) {
self.operation = operation


super.init()
}


func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.3
}


public func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let fromViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from),
let toViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to) else { return }
let containerView = transitionContext.containerView


if operation == .push {
// do your animation for push
} else if operation == .pop {
// do your animation for pop
}
}
}

重要的是要记住,对于每个不同类型的操作(例如,“push”或“pop”),to和from视图控制器将是不同的。当你在进行推送操作时,to视图控制器将是被推送的那个。当你在弹出操作中,to视图控制器将是被转换到的那个,而from视图控制器将是被弹出的那个。

另外,to视图控制器必须在转换上下文中作为containerView的子视图添加。

当动画完成时,必须调用transitionContext.completeTransition(true)。如果您正在进行交互式转换,则必须动态地将Bool返回到completeTransition(didComplete: Bool),这取决于转换是否在动画结束时完成。

最后(可选的阅读),你可能想看看我是如何进行转换的。这段代码有点粗糙,我写得很快,所以我不会说它是伟大的动画代码,但它仍然显示了如何做动画部分。

我的转变非常简单;我想模仿UINavigationController通常做的相同的动画,但不是“下一页上方”动画,我想在新视图控制器出现的同时实现旧视图控制器的1:1动画。这使得两个视图控制器看起来好像是固定在一起的。

对于推操作,首先需要在屏幕外的x轴上设置toViewController的视图原点,并将其添加为containerView的子视图,通过将origin.x设置为0将其动画化到屏幕上。同时,我将fromViewController的视图动画化,方法是将它的origin.x设置在屏幕之外:

toViewController.view.frame = containerView.bounds.offsetBy(dx: containerView.frame.size.width, dy: 0.0)


containerView.addSubview(toViewController.view)


UIView.animate(withDuration: transitionDuration(using: transitionContext),
delay: 0,
options: [ UIViewAnimationOptions.curveEaseOut ],
animations: {
toViewController.view.frame = containerView.bounds
fromViewController.view.frame = containerView.bounds.offsetBy(dx: -containerView.frame.size.width, dy: 0)
},
completion: { (finished) in
transitionContext.completeTransition(true)
})

弹出操作基本上是相反的。添加toViewController作为containerView的子视图,当你在toViewController中从左边进行动画时,将fromViewController动画移向右边:

containerView.addSubview(toViewController.view)


UIView.animate(withDuration: transitionDuration(using: transitionContext),
delay: 0,
options: [ UIViewAnimationOptions.curveEaseOut ],
animations: {
fromViewController.view.frame = containerView.bounds.offsetBy(dx: containerView.frame.width, dy: 0)
toViewController.view.frame = containerView.bounds
},
completion: { (finished) in
transitionContext.completeTransition(true)
})

以下是整个swift文件的要点:

https://gist.github.com/alanzeino/603293f9da5cd0b7f6b60dc20bc766be

你现在可以使用UIView.transition。注意animated:false.;这适用于任何转换选项,弹出,推或堆栈替换。

if let nav = self.navigationController
{
UIView.transition(with:nav.view, duration:0.3, options:.transitionCrossDissolve, animations: {
_ = nav.popViewController(animated:false)
}, completion:nil)
}

现代2022代码。

如何改变一个基于导航的应用程序的推送和弹出动画…

如果你是iOS开发新手。对于最简单、最常见的动画(如“滑动”;或者“一个推另一个”)你必须做大量的工作。

1. 你需要一个自定义UIViewControllerAnimatedTransitioning

  1. 你需要popStyle布尔值-它是弹出打开,还是弹出关闭?

  2. 必须包括transitionDuration(平凡)和主调用animateTransition

  3. < >强必须< / >强编写了两个不同的动画例程,一个用于推送,一个用于弹出。在animateTransition内部,简单地将布尔型popStyle分支到两个例程中的一个

  4. 下面的例子做了一个简单的移动/移动

  5. 在你的animatePushanimatePop例程。你< >强必须< / >强得到"from view"还有&;to view&;。(如何做到这一点,在代码示例中显示。)

  6. 和你< >强必须< / >强 addSubview为新的"to"视图。

  7. 和你< >强必须< / >强调用completeTransition在你的动画结束

复制粘贴…

  class SimpleOver: NSObject, UIViewControllerAnimatedTransitioning {
        

var popStyle: Bool = false
        

func transitionDuration(
using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.20
}
        

func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
            

if popStyle {
                

animatePop(using: transitionContext)
return
}
            

let fz = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)!
let tz = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)!
            

let f = transitionContext.finalFrame(for: tz)
            

let fOff = f.offsetBy(dx: f.width, dy: 55)
tz.view.frame = fOff
            

transitionContext.containerView.insertSubview(tz.view, aboveSubview: fz.view)
            

UIView.animate(
withDuration: transitionDuration(using: transitionContext),
animations: {
tz.view.frame = f
}, completion: {_ in
transitionContext.completeTransition(true)
})
}
        

func animatePop(using transitionContext: UIViewControllerContextTransitioning) {
            

let fz = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)!
let tz = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)!
            

let f = transitionContext.initialFrame(for: fz)
let fOffPop = f.offsetBy(dx: f.width, dy: 55)
            

transitionContext.containerView.insertSubview(tz.view, belowSubview: fz.view)
            

UIView.animate(
withDuration: transitionDuration(using: transitionContext),
animations: {
fz.view.frame = fOffPop
}, completion: {_ in
transitionContext.completeTransition(true)
})
}
}

然后……

2. 在视图控制器中使用它。

注:奇怪的是,你只需要这样做在"first"视图控制器。(“下面”的那个。)

对于你在上弹出的那个,执行没有什么。一件容易的事。

所以你的班级…

class SomeScreen: UIViewController {
}

变得……

class FrontScreen: UIViewController,
UIViewControllerTransitioningDelegate, UINavigationControllerDelegate {
    

let simpleOver = SimpleOver()
    



override func viewDidLoad() {
        

super.viewDidLoad()
navigationController?.delegate = self
}


func navigationController(
_ navigationController: UINavigationController,
animationControllerFor operation: UINavigationController.Operation,
from fromVC: UIViewController,
to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        

simpleOver.popStyle = (operation == .pop)
return simpleOver
}
}

就是这样。

和平常一样,没有变化。推…

let n = UIStoryboard(name: "nextScreenStoryboardName", bundle: nil)
.instantiateViewController(withIdentifier: "nextScreenStoryboardID")
as! NextScreen
navigationController?.pushViewController(n, animated: true)

要弹出它,你可以在下一个屏幕中这样做:

class NextScreen: TotallyOrdinaryUIViewController {
    

@IBAction func userClickedBackOrDismissOrSomethingLikeThat() {
        

navigationController?.popViewController(animated: true)
}
}

唷。

非常简单

self.navigationController?.view.semanticContentAttribute = .forceRightToLeft

基于jordanperry回答为swift 4更新

用于推UIViewController

let yourVC = self.storyboard?.instantiateViewController(withIdentifier: "yourViewController") as! yourViewController
UIView.animate(withDuration: 0.75, animations: {() -> Void in
UIView.setAnimationCurve(.easeInOut)
self.navigationController?.pushViewController(terms, animated: true)
UIView.setAnimationTransition(.flipFromRight, for: (self.navigationController?.view)!, cache: false)
})

为流行

UIView.animate(withDuration: 0.75, animations: {() -> Void in
UIView.setAnimationCurve(.easeInOut)
UIView.setAnimationTransition(.flipFromLeft, for: (self.navigationController?.view)!, cache: false)
})
navigationController?.popViewController(animated: false)

@Luca Davanzo的答案在斯威夫特4.2

public extension UINavigationController {


/**
Pop current view controller to previous view controller.


- parameter type:     transition animation type.
- parameter duration: transition animation duration.
*/
func pop(transitionType type: CATransitionType = .fade, duration: CFTimeInterval = 0.3) {
self.addTransition(transitionType: type, duration: duration)
self.popViewController(animated: false)
}


/**
Push a new view controller on the view controllers's stack.


- parameter vc:       view controller to push.
- parameter type:     transition animation type.
- parameter duration: transition animation duration.
*/
func push(viewController vc: UIViewController, transitionType type: CATransitionType = .fade, duration: CFTimeInterval = 0.3) {
self.addTransition(transitionType: type, duration: duration)
self.pushViewController(vc, animated: false)
}


private func addTransition(transitionType type: CATransitionType = .fade, duration: CFTimeInterval = 0.3) {
let transition = CATransition()
transition.duration = duration
transition.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
transition.type = type
self.view.layer.add(transition, forKey: nil)
}


}

我知道这个帖子很旧了,但我想我应该发表我的意见。你不需要制作一个自定义动画,有一个简单(可能是hack)的方法来做它。不使用push,而是创建一个新的导航控制器,使新的视图控制器成为那个nav控制器的根视图控制器,然后从原始的nav控制器中呈现出nav控制器。现在是很容易自定义的许多风格,不需要做一个自定义动画。

例如:

UIViewcontroller viewControllerYouWantToPush = UIViewController()
UINavigationController newNavController = UINavigationController(root: viewControllerYouWantToView)
newNavController.navBarHidden = YES;
self.navigationController.present(newNavController)

你可以随心所欲地改变演示风格。