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

编辑2018

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


当前回答

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

您可以使用不同的预定义动画推送和弹出动作,如滑动,淡出,立方体,旋转,缩放等。

其他回答

现代2022代码。

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

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

1. 你需要一个自定义的uiviewcontrolleranimatedtransiting

You need popStyle boolean - is it popping on, or popping off? You must include transitionDuration (trivial) and the main call, animateTransition You must write the two different animations routines, one for the push, and one for the pop. Inside animateTransition, simply branch on the boolean popStyle to one the two routines The example below does a simple move-over/move-off In your animatePush and animatePop routines. You must get the "from view" and the "to view". (How to do that, is shown in the code example.) and you must addSubview for the new "to" view. and you must call completeTransition at the end of your anime

复制粘贴…

  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)
    }
}

唷。

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

如果“后退”按钮不是必需的,你应该使用模态视图控制器来实现“push from bottom”/“flip”/“fade”/(≥3.2)“页面卷曲”过渡。


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

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

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

@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做这个,只需要在prepareForSegue或shouldPerformSegueWithIdentifier中实现。但是,这也会保留默认动画。要解决这个问题,你必须转到故事板,单击Segue,取消选中“Animates”框。但这将限制你的应用在IOS 9.0及以上(至少当我在Xcode 7中这样做时)。

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

self.navigationController?.popViewControllerAnimated(false)

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

由于这是谷歌上的排名第一的结果,我想我要分享我认为是最理智的方式;也就是使用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

使用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];