How to change the Push and Pop animations in a navigation based app

前端 未结 25 1157
清酒与你
清酒与你 2020-11-22 12:46

I have a navigation based application and I want to change the animation of the push and pop animations. How would I do that?

Edit 2018

Ther

相关标签:
25条回答
  • 2020-11-22 13:12

    Since this is the top result on Google I thought I'd share what I think is the most sane way; which is to use the iOS 7+ transitioning API. I implemented this for iOS 10 with Swift 3.

    It's pretty simple to combine this with how UINavigationController animates between two view controllers if you create a subclass of UINavigationController and return an instance of a class that conforms to the UIViewControllerAnimatedTransitioning protocol.

    For example here is my UINavigationController subclass:

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

    You can see that I set the UINavigationControllerDelegate to itself, and in an extension on my subclass I implement the method in UINavigationControllerDelegate that allows you to return a custom animation controller (i.e., NavigationControllerAnimation). This custom animation controller will replace the stock animation for you.

    You're probably wondering why I pass in the operation to the NavigationControllerAnimation instance via its initializer. I do this so that in NavigationControllerAnimation's implementation of the UIViewControllerAnimatedTransitioning protocol I know what the operation is (i.e., 'push' or 'pop'). This helps to know what kind of animation I should do. Most of the time, you want to perform a different animation depending on the operation.

    The rest is pretty standard. Implement the two required functions in the UIViewControllerAnimatedTransitioning protocol and animate however you like:

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

    It's important to remember, that for each different type of operation (i.e., 'push' or 'pop), the to and from view controllers will be different. When you are in a push operation, the to view controller will be the one being pushed. When you are in a pop operation, the to view controller will be the one that is being transitioned to, and the from view controller will be the one that's being popped.

    Also, the to view controller must be added as a subview of the containerView in the transition context.

    When your animation completes, you must call transitionContext.completeTransition(true). If you are doing an interactive transition, you will have to dynamically return a Bool to completeTransition(didComplete: Bool), depending on if the transition is complete at the end of the animation.

    Finally (optional reading), you might want to see how I did the transition I was working on. This code is a bit more hacky and I wrote it pretty quickly so I wouldn't say it's great animation code but it still shows how to do the animation part.

    Mine was a really simple transition; I wanted to mimic the same animation that UINavigationController typically does, but instead of the 'next page over the top' animation it does, I wanted to implement a 1:1 animation of the old view controller away at the same time as the new view controller appears. This has the effect of making the two view controllers seem as though they are pinned to each other.

    For the push operation, that requires first setting the toViewController's view origin on the x–axis off screen, adding it as the subview of the containerView, animating it onto screen by setting that origin.x to zero. At the same time, I animate the fromViewController's view away by setting its origin.x off the screen:

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

    The pop operation is basically the inverse. Add the toViewController as a subview of the containerView, and animate away the fromViewController to the right as you animate in the toViewController from the left:

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

    Here's a gist with the whole swift file:

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

    0 讨论(0)
  • 2020-11-22 13:13

    Here is how I have done the same in Swift:

    For Push:

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

    For Pop:

    I actually did this a little differently to some of the responses above - but as I am new to Swift development, it might not be right. I have overridden viewWillDisappear:animated: and added the pop code in there:

        UIView.animateWithDuration(0.75, animations: { () -> Void in
            UIView.setAnimationCurve(UIViewAnimationCurve.EaseInOut)
            UIView.setAnimationTransition(UIViewAnimationTransition.FlipFromLeft, forView: self.navigationController!.view, cache: false)
        })
    
        super.viewWillDisappear(animated)
    
    0 讨论(0)
  • 2020-11-22 13:14

    I know this thread is old, but I thought I'd put in my two cents. You don't need to make a custom animation, there's a simple (maybe hacky) way of doing it. Instead of using push, create a new navigation controller, make the new view controller the root view controller of that nav controller, and then present the nav controller from the original nav controller. Present is easily customizable with many styles, and no need to make a custom animation.

    For example:

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

    And you can change the presentation style however you want.

    0 讨论(0)
  • 2020-11-22 13:16

    @Magnus answer, only then for 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)
    

    Some sidenotes:

    You can do this as well with Segue, just implement this in prepareForSegue or shouldPerformSegueWithIdentifier. However, this will keep the default animation in it as well. To fix this you have to go to the storyboard, click the Segue, and uncheck the box 'Animates'. But this will limit your app for IOS 9.0 and above (atleast when I did it in Xcode 7).

    When doing in a segue, the last two lines should be replaced with:

    self.navigationController?.popViewControllerAnimated(false)
    

    Even though I set false, it kind of ignores it.

    0 讨论(0)
  • 2020-11-22 13:18

    I am not aware of any way you can change the transition animation publicly.

    If the "back" button is not necessary you should use modal view controllers to have the "push from bottom" / "flip" / "fade" / (≥3.2)"page curl" transitions.


    On the private side, the method -pushViewController:animated: calls the undocumented method -pushViewController:transition:forceImmediate:, so e.g. if you want a flip-from-left-to-right transition, you can use

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

    You can't change the "pop" transition this way, however.

    0 讨论(0)
  • 2020-11-22 13:18

    Just use:

    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];
    
    0 讨论(0)
提交回复
热议问题