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

前端 未结 25 1139
清酒与你
清酒与你 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:02

    @Luca Davanzo's answer in Swift 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)
        }
    
    }
    
    0 讨论(0)
  • 2020-11-22 13:05

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

    For 2019, the "final answer!"

    Preamble:

    Say you are new to iOS development. Confusingly, Apple provide two transitions which can be used easily. These are: "crossfade" and "flip".

    But of course, "crossfade" and "flip" are useless. They are never used. Nobody knows why Apple provided those two useless transitions!

    So:

    Say you want to do an ordinary, common, transition such as "slide". In that case, you have to do a HUGE amount of work!.

    That work, is explained in this post.

    Just to repeat:

    Surprisingly: with iOS, if you want the simplest, most common, everyday transitions (such as an ordinary slide), you do have to all the work of implementing a full custom transition.

    Here's how to do it ...

    1. You need a custom UIViewControllerAnimatedTransitioning

    1. You need a bool of your own like popStyle . (Is it popping on, or popping off?)

    2. You must include transitionDuration (trivial) and the main call, animateTransition

    3. In fact you must write two different routines for inside animateTransition. One for the push, and one for the pop. Probably name them animatePush and animatePop. Inside animateTransition, just branch on popStyle to the two routines

    4. The example below does a simple move-over/move-off

    5. 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.)

    6. and you must addSubview for the new "to" view.

    7. and you must call completeTransition at the end of your anime

    So ..

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

    And then ...

    2. Use it in your view controller.

    Note: strangely, you only have to do this in the "first" view controller. (The one which is "underneath".)

    With the one that you pop on top, do nothing. Easy.

    So your class...

    class SomeScreen: UIViewController {
    }
    

    becomes...

    class FrontScreen: UIViewController,
            UIViewControllerTransitioningDelegate, UINavigationControllerDelegate {
        
        let simpleOver = SimpleOver()
        
    
        override func viewDidLoad() {
            
            super.viewDidLoad()
            navigationController?.delegate = self
        }
    
        func navigationController(
            _ navigationController: UINavigationController,
            animationControllerFor operation: UINavigationControllerOperation,
            from fromVC: UIViewController,
            to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
            
            simpleOver.popStyle = (operation == .pop)
            return simpleOver
        }
    }
    

    That's it.

    Push and pop exactly as normal, no change. To push ...

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

    and to pop it, you can if you like just do that on the next screen:

    class NextScreen: TotallyOrdinaryUIViewController {
        
        @IBAction func userClickedBackOrDismissOrSomethingLikeThat() {
            
            navigationController?.popViewController(animated: true)
        }
    }
    

    Phew.


    3. Also enjoy the other answers on this page which explain how to override AnimatedTransitioning

    Scroll to @AlanZeino and @elias answer for more discussion on how to AnimatedTransitioning in iOS apps these days!

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

    It's very simple

    self.navigationController?.view.semanticContentAttribute = .forceRightToLeft
    
    0 讨论(0)
  • 2020-11-22 13:06

    See my answer to this question for a way to do it in far fewer lines of code. This method allows you to animate a pseudo-"Push" of a new view controller any way you like, and when the animation is done it sets up the Navigation Controller just as if you had used the standard Push method. My example lets you animate either a slide-in from the left or from the right. Code repeated here for convenience:

    -(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];
        }];
    }
    
    0 讨论(0)
  • 2020-11-22 13:10

    Using iJordan's answer as inspiration, why not simply create a Category on UINavigationController to use throughout your app instead of copying/pasting this animation code all over the place?

    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
    

    Then simply import the UINavigationController+Animation.h file and call it normally:

    [self.navigationController pushViewControllerWithFlip:[[NewViewController alloc] init]];
    
    [self.navigationController popViewControllerWithFlip];
    
    0 讨论(0)
  • 2020-11-22 13:11

    I did the following and it works fine.. and is simple and easy to understand..

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

    And the same thing for push..


    Swift 3.0 version:

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