it-swarm.com.de

So ändern Sie die Push- und Pop-Animationen in einer navigationsbasierten App

Ich habe eine navigationsbasierte Anwendung und möchte die Animation der Push- und Pop-Animationen ändern. Wie würde ich das machen?

Bearbeiten Sie 2018

Es gab viele Antworten auf diese Frage, und es ist schon eine Weile her, dass ich die Antwort auf das, was ich für das relevanteste halte, erneut gewählt habe. Wenn jemand anders denkt, lass es mich bitte in Kommentaren wissen

204
Jab

So ändern Sie die Push- und Pop-Animationen in einer navigationsbasierten App ...

Für 2018, err 2019, die "endgültige Antwort!"

Präambel _ ​​Sagen Sie, Sie sind ein Neuling in der iOS-Entwicklung, vielleicht von Android. Verwirrenderweise bieten Apple zwei (und nur zwei) Übergänge an, die problemlos verwendet werden können. Dies sind: "Crossfade" und "Flip". Die beiden nutzlosesten Übergänge sind "crossfade" und "flip". Niemand weiß, warum Apple diese beiden nutzlosen Übergänge als eingebaute Übergänge bereitgestellt hat. Also: überraschenderweise, wenn Sie die üblichsten Übergänge machen möchten, wie z "Abgleiten", Sie müssen eine RIESIGE Menge Arbeit erledigen. Diese Arbeit wird in diesem Beitrag erklärt.

  • Hinweis 1: Wenn Sie eine der beiden von Apple angebotenen Optionen (Crossfade, Flips) verwenden möchten, lesen Sie die Antwort von @PeterDeWeese auf dieser Seite.

  • Anmerkung 2: Vor einigen Jahren gab es einen quasi-schnellen Übergang in CATransition. Es funktioniert nicht.

Also um es zu wiederholen:

Überraschenderweise: Wenn Sie unter iOS nur den einfachsten Übergang wünschen, z. B. einen gewöhnlichen Slide-Over/Slide-Off-Übergang, müssen Sie einen vollständigen benutzerdefinierten Übergang implementieren.

So geht's ...

1. Sie benötigen eine benutzerdefinierte UIViewControllerAnimatedTransitioning

  1. Sie benötigen einen eigenen Bool wie popStyle. (Knallt es auf oder knallt ab?)

  2. Sie müssen transitionDuration (trivial) und den Hauptaufruf animateTransition einschließen.

  3. Tatsächlich schreiben Sie must zwei verschiedene Routinen für animateTransition. Eine für den Push und eine für den Pop. Nennen Sie sie wahrscheinlich animatePush und animatePop. Verzweigen Sie in animateTransition zu popStyle zu den beiden Routinen

  4. Das folgende Beispiel führt einen einfachen Über-/Abfahrvorgang aus

  5. In Ihren animatePush- und animatePop-Routinen. Sie erhalten must das "from view" und das "to view". (Wie das geht, wird im Codebeispiel gezeigt.)

  6. und Sie mustaddSubview für die neue "to" -Ansicht.

  7. und Sie must rufen am Ende Ihres Anime completeTransition auf

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

Und dann ...

2. Verwenden Sie es in Ihrem View-Controller.

Anmerkung: Seltsamerweise sind Sie müssen nur das tun im "ersten" View-Controller. (Die, die "darunter" ist.)

Mit dem, den Sie auf top platzieren, tun Sie nothing. Einfach.

Also deine Klasse ...

class SomeScreen: UIViewController {
}

wird...

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

Das ist es.

Push und Pop genau wie normal, keine Änderung. Drücken ...

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

und um es zu knacken, können Sie dies auf dem nächsten Bildschirm tun:

class NextScreen: TotallyOrdinaryUIViewController {

    @IBAction func userClickedBackOrDismissOrSomethingLikeThat() {

        navigationController?.popViewController(animated: true)
    }
}

"So einfach ist das."

18
Fattie

Ich habe folgendes gemacht und es funktioniert gut .. und ist einfach und leicht zu verstehen.

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

Und das Gleiche für 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)
258
Magnus

So habe ich es immer geschafft, diese Aufgabe zu erledigen.

Für Push:

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

Für Pop:

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


Ich bekomme immer noch eine Menge Feedback, daher werde ich es weiter aktualisieren, um Animationsblöcke zu verwenden. Dies ist der von Apple empfohlene Weg, Animationen zu machen.

Für 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];
                         }];

Für Pop:

[UIView animateWithDuration:0.75
                         animations:^{
                             [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
                             [UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft forView:self.navigationController.view cache:NO];
                         }];
[self.navigationController popViewControllerAnimated:NO];
256
jordanperry

für Push

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

für Pop

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

@Magnus Antwort, nur für 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)

Einige Nebenbemerkungen:

Sie können dies auch mit Segue tun, implementieren Sie dies einfach in prepareForSegue oder shouldPerformSegueWithIdentifier. Allerdings , dies behält auch die Standardanimation bei. Um dieses Problem zu beheben, müssen Sie zum Storyboard gehen, auf das Segue klicken und das Kontrollkästchen "Animates" deaktivieren. Dies beschränkt Ihre App jedoch auf IOS 9.0 und höher (zumindest wenn ich es in Xcode 7 gemacht habe).

In einem Segue sollten die letzten beiden Zeilen ersetzt werden durch:

self.navigationController?.popViewControllerAnimated(false)

Obwohl ich falsch gesetzt habe, ignoriert es es irgendwie.

18
CularBytes

Denken Sie daran, dass in Swift , extension definitiv Ihre Freunde sind! 

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

}
15
Luca Davanzo

Die Verwendung von Privatgesprächen ist eine schlechte Idee, da Apple Apps, die dies tun, nicht mehr zulässt.

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

Da dies das beste Ergebnis bei Google ist, dachte ich, ich würde teilen, was ich für den vernünftigsten Weg halte. Das ist die iOS 7+ -Übergangs-API. Ich habe dies für iOS 10 mit Swift 3 implementiert.

Es ist ziemlich einfach zu kombinieren, wie UINavigationController zwischen zwei View-Controllern animiert wird, wenn Sie eine Unterklasse von UINavigationController erstellen und eine Instanz einer Klasse zurückgeben, die dem UIViewControllerAnimatedTransitioning-Protokoll entspricht.

Zum Beispiel ist hier meine UINavigationController Unterklasse:

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

}

Sie können sehen, dass ich UINavigationControllerDelegate auf sich selbst gesetzt habe, und in einer Erweiterung meiner Unterklasse implementiere ich die Methode in UINavigationControllerDelegate, mit der Sie einen benutzerdefinierten Animationscontroller zurückgeben können (dh NavigationControllerAnimation). . Dieser benutzerdefinierte Animationscontroller ersetzt für Sie die Standardanimation.

Sie fragen sich wahrscheinlich, warum ich die Operation über ihren Initialisierer an die Instanz NavigationControllerAnimation übergeben habe. Ich tue dies, damit ich in NavigationControllerAnimation 's Implementierung des UIViewControllerAnimatedTransitioning-Protokolls weiß, was die Operation ist (d. H.' Push 'oder' Pop '). Dies hilft zu wissen, welche Art von Animation ich machen soll. Meistens möchten Sie je nach Vorgang eine andere Animation ausführen.

Der Rest ist ziemlich normal. Implementieren Sie die zwei erforderlichen Funktionen im UIViewControllerAnimatedTransitioning-Protokoll und animieren Sie, wie Sie möchten:

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

Es ist wichtig zu wissen, dass für jede Art von Operation (z. B. "Push" oder "Pop") die Controller für die und die Ansicht unterschiedlich sind. Wenn Sie sich in einem Push-Vorgang befinden, wird der zu betrachtende Controller derjenige sein, der gedrückt wird. Wenn Sie sich in einem Pop-Vorgang befinden, wird der View-Controller der Controller sein, zu dem gewechselt wird, und der From-View-Controller ist der Controller, der gerade geöffnet wird.

Außerdem muss der View-Controller to als Unteransicht von containerView im Übergangskontext hinzugefügt werden.

Wenn Ihre Animation abgeschlossen ist, müssen Sie transitionContext.completeTransition(true) aufrufen. Wenn Sie einen interaktiven Übergang durchführen, müssen Sie einen Bool dynamisch an completeTransition(didComplete: Bool) zurückgeben, je nachdem, ob der Übergang am Ende der Animation abgeschlossen ist.

Zum Schluss (optionales Lesen) möchten Sie vielleicht sehen, wie ich den Übergang vollbrachte, an dem ich arbeitete. Dieser Code ist etwas hackiger und ich habe ihn ziemlich schnell geschrieben, also würde ich nicht sagen, dass er großartiger Animationscode ist, aber er zeigt immer noch, wie man den Animationsteil macht.

Meines war ein wirklich einfacher Übergang. Ich wollte die gleiche Animation nachahmen, die UINavigationController normalerweise macht, aber anstelle der Animation "Nächste Seite über dem oberen Rand" wollte ich eine 1: 1-Animation des alten View-Controllers gleichzeitig mit der neuen Ansicht implementieren Controller erscheint. Dies hat den Effekt, dass die beiden View-Controller so aussehen, als wären sie aneinandergeheftet.

Für die Push-Operation müssen Sie zunächst die Ansicht Origin von toViewController auf der X-Achse außerhalb des Bildschirms festlegen, diese als Unteransicht von containerView hinzufügen und sie auf dem Bildschirm animieren, indem Sie Origin.x auf Null setzen. Zur gleichen Zeit animiere ich die Ansicht von fromViewController, indem ich Origin.x aus dem Bildschirm schiebe: 

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

Die Pop-Operation ist im Wesentlichen die Umkehrung. Fügen Sie toViewController als Unteransicht von containerView hinzu, und animieren Sie fromViewController nach rechts, während Sie in toViewController von links animieren:

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

Hier ist ein Gist mit der ganzen Swift-Datei:

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

8
Alan Zeino

Es gibt UINavigationControllerDelegate und UIViewControllerAnimatedTransitioning. Dort können Sie die Animation für jedes beliebige Element ändern.

Dies ist zum Beispiel eine vertikale Pop-Animation für 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())
    })
}

}

Und dann

func navigationController(navigationController: UINavigationController, animationControllerForOperation operation: UINavigationControllerOperation, fromViewController fromVC: UIViewController, toViewController toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
    if operation == .Pop {
        return PopAnimator()
    }
    return nil;
}

Nützliches Tutorial https://www.objc.io/issues/5-ios7/view-controller-transitions/

6
eilas

Basierend auf jordanperryantwort aktualisiert für Swift 4

Für Push 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)
})

Für Pop

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

So habe ich dasselbe in Swift gemacht:

Für 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)
    })

Für Pop:

Ich habe das tatsächlich ein bisschen anders gemacht als einige der obigen Antworten - aber da ich neu in der Swift-Entwicklung bin, ist es vielleicht nicht richtig. Ich habe viewWillDisappear:animated: überschrieben und dort den Popcode eingefügt:

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

    super.viewWillDisappear(animated)
5
djbp

Ich habe vor kurzem versucht, etwas Ähnliches zu tun. Ich entschied, dass mir die gleitende Animation des UINavigationControllers nicht gefallen hat, aber ich wollte auch nicht die Animationen machen, die UIView Ihnen als Curl oder ähnliches gibt. Ich wollte eine Überblendung zwischen den Ansichten, wenn ich Push oder Pop mache. 

Das Problem besteht darin, dass die Ansicht die Ansicht buchstäblich entfernt oder über die aktuelle Ansicht springt, sodass ein Fade nicht funktioniert. Die Lösung, die ich erhielt, bestand darin, meine neue Ansicht zu übernehmen und sie als Unteransicht zur aktuellen Draufsicht auf den UIViewController-Stack hinzuzufügen. Ich füge es mit einem Alpha von 0 hinzu und füge dann eine Überblendung ein. Wenn die Animationssequenz abgeschlossen ist, schiebe ich die Ansicht auf den Stapel, ohne sie zu animieren. Ich gehe dann zum alten topView zurück und räume Sachen auf, die ich geändert habe.

Es ist etwas komplizierter als das, weil Sie die navigationItems haben, die Sie anpassen müssen, damit der Übergang richtig aussieht. Wenn Sie eine Drehung vornehmen, müssen Sie die Framegröße anpassen, wenn Sie die Ansichten als Unteransichten hinzufügen, damit sie korrekt auf dem Bildschirm angezeigt werden. Hier ist ein Teil des Codes, den ich verwendet habe. Ich habe den UINavigationController als Unterklasse klassifiziert und die Push- und die Pop-Methode überschrieben.

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

 }

Wie ich im Code erwähne, habe ich immer ein Element für die linke Balkenschaltfläche. Ich überprüfe also nicht, ob es null ist, bevor ich es in das Array setze, das ich als Kontext für den Animationsdelegierten übergeben habe. Wenn Sie dies tun, möchten Sie möglicherweise diese Überprüfung durchführen. 

Das Problem, das ich gefunden habe, war, dass das Programm nicht abstürzt, wenn Sie überhaupt in der Delegat-Methode abstürzen. Der Delegierte wird lediglich angehalten, aber es wird keine Warnung angezeigt.
Da ich meine Reinigung in dieser Delegiertenroutine durchführte, verursachte dies ein seltsames visuelles Verhalten, da die Reinigung nicht beendet wurde.

Die Zurück-Schaltfläche, die ich erstelle, ruft eine "goBack" -Methode auf, und diese Methode ruft nur die Pop-Routine auf.

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

Hier ist auch meine Pop-Routine.

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


 }
}

Das wars so ziemlich. Sie benötigen zusätzlichen Code, wenn Sie Rotationen implementieren möchten. Sie müssen die Framegröße Ihrer Ansichten festlegen, die Sie als Unteransichten hinzufügen, bevor Sie sie anzeigen. Andernfalls werden Sie auf Probleme stoßen, deren Ausrichtung Querformat ist. Als Sie das letzte Mal die vorherige Ansicht gesehen haben, war das Hochformat. Dann fügen Sie es als Unteransicht hinzu und blenden es ein, aber es wird als Porträt angezeigt. Wenn Sie ohne Animation auftauchen, wird dieselbe Ansicht angezeigt. Die Ansicht, die sich im Stapel befindet, ist jetzt Querformat. Das Ganze sieht ein bisschen funky aus. Die Implementierung von Rotation ist bei allen etwas anders, daher habe ich meinen Code hier nicht eingefügt.

Hoffe, es hilft einigen Leuten. Ich habe überall nach so etwas gesucht und konnte nichts finden. Ich denke nicht, dass dies die perfekte Antwort ist, aber es funktioniert zu diesem Zeitpunkt wirklich gut für mich.

4
georryan

Verwenden Sie die Antwort von iJordan als Inspiration. Warum erstellen Sie nicht einfach eine Kategorie in UINavigationController, die Sie in Ihrer App verwenden können, anstatt diesen Animationscode überall zu kopieren/einzufügen?

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

Dann einfach die Datei UINavigationController + Animation.h importieren und normal aufrufen:

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

[self.navigationController popViewControllerWithFlip];
3
DiscDev

Während alle Antworten hier großartig sind und die meisten sehr gut funktionieren, gibt es eine etwas einfachere Methode, die den gleichen Effekt erzielt ...

Für Push:

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

Für Pop:

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

Sie können jetzt UIView.transition verwenden. Beachten Sie, dass animated:false. Dies funktioniert mit jeder Übergangsoption, Pop, Push oder Stack-Ersetzung.

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

Es ist sehr einfach

self.navigationController?.view.semanticContentAttribute = .forceRightToLeft
2
user2501116

Werfen Sie einen Blick auf ADTransitionController , einen Ersatz für UINavigationController mit benutzerdefinierten Übergangsanimationen (seine API entspricht der API von UINavigationController), die wir bei Applidium erstellt haben. 

Sie können verschiedene vordefinierte Animationen für die Aktionen Push und pop verwenden, z. B. Swipe, Fade, Cube, Carrousel, Zoom und so weiter.

2
felginep

Siehe meine Antwort auf diese Frage für eine Möglichkeit, dies in weit weniger Codezeilen zu tun. Mit dieser Methode können Sie ein Pseudo-Push eines neuen View-Controllers auf beliebige Weise animieren. Wenn die Animation abgeschlossen ist, wird der Navigations-Controller so eingerichtet, als hätten Sie die Standard-Push-Methode verwendet. In meinem Beispiel können Sie entweder ein Slide-In von links oder von rechts animieren Der Code wird hier zur Vereinfachung wiederholt:

-(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];
    }];
}
1
RobP

Mir ist nicht bekannt, wie Sie die Übergangsanimation öffentlich ändern können. 

Wenn die Schaltfläche "Zurück" nicht erforderlich ist, müssen Sie solltenModal View Controller verwenden, um "Push from bottom"/"flip"/"fade"/(≥3.2) zu erhalten. "Seitenrotation" Übergänge.


Auf der Seite private ruft die Methode -pushViewController:animated: die undokumentierte Methode -pushViewController:transition:forceImmediate: auf, also z. Wenn Sie einen Übergang von links nach rechts machen möchten, können Sie verwenden

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

Sie können den "Pop" -Übergang jedoch nicht auf diese Weise ändern.

0
kennytm

@Luca Davanzos Antwort 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
Serj Rubens

Überprüfen Sie in der Beispiel-App diese Variation aus . 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;
}
0
johndpope

Das zu realisieren ist eine alte Frage. Ich möchte diese Antwort trotzdem posten, da ich Probleme hatte, mehrere viewControllers mit den vorgeschlagenen Antworten zu knacken. Meine Lösung ist die Unterklasse UINavigationController und das Überschreiben aller Pop- und Push-Methoden.

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
0

Ich weiß, dieser Thread ist alt, aber ich dachte, ich würde meine zwei Cent setzen. Sie müssen keine benutzerdefinierte Animation erstellen, es gibt eine einfache (möglicherweise hackige) Möglichkeit, dies zu tun. Anstatt Push zu verwenden, erstellen Sie einen neuen Navigationscontroller, machen Sie den neuen Ansichtscontroller zum Stammansichtscontroller dieses Navigationscontrollers und zeigen Sie den Navigationscontroller vom ursprünglichen Navigationscontroller an. Present lässt sich leicht mit vielen Stilen anpassen und es ist keine benutzerdefinierte Animation erforderlich.

Zum Beispiel:

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

Und Sie können den Präsentationsstil nach Belieben ändern.

0
Ethan Zhao

Benutz einfach:

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