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

编辑2018

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


当前回答

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

推动:

  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];
  }];}

其他回答

从示例应用程序中,可以看到这种变化。 https://github.com/mpospese/MPFoldTransition/

#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];
  }];}

请参阅我对这个问题的回答,以了解如何用更少的代码行完成它。这个方法允许你以任何你喜欢的方式动画一个新视图控制器的伪“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];
    }];
}

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

//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];

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

如果你创建UINavigationController的子类并返回一个符合uiviewcontrolleranimatedtransioning协议的类的实例,那么将这个与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实例的初始化器将操作传递给它。我这样做是为了在NavigationControllerAnimation的uiviewcontrolleranimatedtransiating协议的实现中,我知道操作是什么(即“push”或“pop”)。这有助于知道我应该做什么样的动画。大多数情况下,您希望根据操作执行不同的动画。

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

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动画。这使得两个视图控制器看起来好像是固定在一起的。

对于push操作,首先需要在屏幕外的x轴上设置toViewController的视图原点,将其添加为containerView的子视图,通过设置该原点将其动画化到屏幕上。X到0。与此同时,我通过设置fromViewController的原点来使它动画化。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的子视图,并将fromViewController动画移到右边,就像你从左边动画toViewController一样:

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